Hi Honza, Martin et al, Latest patches attached. On top of previous patches (most review matters addressed**) patches 0008..0011 add support for profiles and user certificates to `ipa cert-request'.
** those that were not are being tracked at [1]; please add anything I missed. Some points to note: - usercertificate is not yet a multi-valued attribute for users, hosts and services. QUESTION - we do want to allow multiple certificates for all principal types, not just users? Or have I got that wrong. - "DN and SAN match principal" checks are not implemented for users yet. - ACL was added to allow user principals to request their own certificates, however, this will be further subject to CA/profile ACLs which are to come. - Pursuant to [2] revocation logic was removed from `cert-request' [1] http://idm.etherpad.corp.redhat.com/rhel72-cert-mgmt-progress [2] http://www.freeipa.org/page/V4/User_Certificates#Revocation_of_the_Certificates Thanks, Fraser
>From 07599bb290fdd23990c978489bc3b08d493caed6 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 20 Apr 2015 23:20:19 -0400 Subject: [PATCH 01/11] Install CA with LDAP profiles backend Install the Dogtag CA to use the LDAPProfileSubsystem instead of the default (file-based) ProfileSubsystem. Part of: https://fedorahosted.org/freeipa/ticket/4560 --- ipaserver/install/cainstance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 5133940687204b615eec56b6a89542ddd5617539..030c9f12daba4b38b748da8940e38d3cf2109788 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -503,6 +503,7 @@ class CAInstance(DogtagInstance): config.set("CA", "pki_restart_configured_instance", "False") config.set("CA", "pki_backup_keys", "True") config.set("CA", "pki_backup_password", self.admin_password) + config.set("CA", "pki_profiles_in_ldap", "True") # Client security database config.set("CA", "pki_client_database_dir", self.agent_db) -- 2.1.0
>From c1fafbce7b077c6981381b727f1dc9571ccf2f19 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Tue, 21 Apr 2015 02:24:10 -0400 Subject: [PATCH 02/11] Add schema for certificate profiles The certprofile object class is used to track IPA-managed certificate profiles in Dogtag and store IPA-specific settings. Part of: https://fedorahosted.org/freeipa/ticket/57 --- install/share/60certificate-profiles.ldif | 3 +++ install/share/Makefile.am | 1 + install/share/bootstrap-template.ldif | 12 ++++++++++++ ipaserver/install/dsinstance.py | 1 + 4 files changed, 17 insertions(+) create mode 100644 install/share/60certificate-profiles.ldif diff --git a/install/share/60certificate-profiles.ldif b/install/share/60certificate-profiles.ldif new file mode 100644 index 0000000000000000000000000000000000000000..dcf4680589c98dad165141b1e13946c161a6abd7 --- /dev/null +++ b/install/share/60certificate-profiles.ldif @@ -0,0 +1,3 @@ +dn: cn=schema +attributeTypes: (2.16.840.1.113730.3.8.19.1.1 NAME 'ipaCertProfileStoreIssued' DESC 'Store certificates issued using this profile' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.2' ) +objectClasses: (2.16.840.1.113730.3.8.19.2.1 NAME 'ipaCertProfile' SUP top STRUCTURAL MUST ( cn $ description $ ipaCertProfileStoreIssued ) X-ORIGIN 'IPA v4.2' ) diff --git a/install/share/Makefile.am b/install/share/Makefile.am index ca6128e2911ab5c0a773dd553f8e67eab944f120..2cae5279079cdd3e0d793667f4d1bf4e44757b9e 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -16,6 +16,7 @@ app_DATA = \ 60basev3.ldif \ 60ipadns.ldif \ 60ipapk11.ldif \ + 60certificate-profiles.ldif \ 61kerberos-ipav3.ldif \ 65ipacertstore.ldif \ 65ipasudo.ldif \ diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif index 06b82aa4ae74e7766d0c09a63aa75fa222e7ab7d..c5d4bad8b80640881f4631e4873a12c82b0ea48a 100644 --- a/install/share/bootstrap-template.ldif +++ b/install/share/bootstrap-template.ldif @@ -429,3 +429,15 @@ cn: ${REALM}_id_range ipaBaseID: $IDSTART ipaIDRangeSize: $IDRANGE_SIZE ipaRangeType: ipa-local + +dn: cn=ca,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: ca + +dn: cn=certprofiles,cn=ca,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: certprofiles diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index f1d24e49d1b184efde1c8d18ff37d0e329037ccc..210992fc41127de29d41b889e33a312613ae28da 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -56,6 +56,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif", "60basev3.ldif", "60ipapk11.ldif", "60ipadns.ldif", + "60certificate-profiles.ldif", "61kerberos-ipav3.ldif", "65ipacertstore.ldif", "65ipasudo.ldif", -- 2.1.0
>From fc1dfcd7f8a9241c81ac97164a6e5699da74aa1b Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Wed, 29 Apr 2015 06:07:58 -0400 Subject: [PATCH 03/11] ipa-pki-proxy: provide access to profiles REST API Part of: https://fedorahosted.org/freeipa/ticket/57 --- install/conf/ipa-pki-proxy.conf | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/install/conf/ipa-pki-proxy.conf b/install/conf/ipa-pki-proxy.conf index 5d21156848f3b5ddf14c42d92a26a30a9f94af36..366ca15a1868758547f9f1d3334fddba38793083 100644 --- a/install/conf/ipa-pki-proxy.conf +++ b/install/conf/ipa-pki-proxy.conf @@ -1,4 +1,4 @@ -# VERSION 5 - DO NOT REMOVE THIS LINE +# VERSION 6 - DO NOT REMOVE THIS LINE ProxyRequests Off @@ -11,7 +11,7 @@ ProxyRequests Off </LocationMatch> # matches for admin port and installer -<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/rest/installer/installToken|^/ca/admin/ca/updateNumberRange|^/ca/rest/securityDomain/domainInfo|^/ca/rest/account/login|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/rest/account/logout|^/ca/rest/securityDomain/installToken|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries|^/kra/rest/config/cert/transport"> +<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/rest/installer/installToken|^/ca/admin/ca/updateNumberRange|^/ca/rest/securityDomain/domainInfo|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/rest/securityDomain/installToken|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries|^/kra/rest/config/cert/transport"> NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate NSSVerifyClient none ProxyPassMatch ajp://localhost:$DOGTAG_PORT @@ -26,5 +26,13 @@ ProxyRequests Off ProxyPassReverse ajp://localhost:$DOGTAG_PORT </LocationMatch> +# matches for REST API +<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/profiles"> + NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate + NSSVerifyClient require + ProxyPassMatch ajp://localhost:$DOGTAG_PORT + ProxyPassReverse ajp://localhost:$DOGTAG_PORT +</LocationMatch> + # Only enable this on servers that are not generating a CRL ${CLONE}RewriteRule ^/ipa/crl/MasterCRL.bin https://$FQDN/ca/ee/ca/getCRL?op=getCRL&crlIssuingPoint=MasterCRL [L,R=301,NC] -- 2.1.0
>From ae5f5dae49ad891864bbdcbc5c2d9f592521fe74 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 30 Apr 2015 23:50:41 -0400 Subject: [PATCH 04/11] Add ACL to allow CA agent to modify profiles Part of: https://fedorahosted.org/freeipa/ticket/57 --- install/tools/ipa-upgradeconfig | 19 +++++++++++++++++++ ipaserver/install/cainstance.py | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig index dfef1e0aa8b1507b7aa4907e9b688ce99253b87c..92c4934874f12c7017329bdcb67ac9a888f6e389 100755 --- a/install/tools/ipa-upgradeconfig +++ b/install/tools/ipa-upgradeconfig @@ -31,6 +31,7 @@ import pwd import fileinput import ConfigParser import grp +import ldap from ipalib import api import SSSDConfig @@ -40,6 +41,7 @@ from ipaplatform import services from ipaplatform.tasks import tasks from ipapython import ipautil, sysrestore, version, certdb from ipapython.config import IPAOptionParser +from ipapython.dn import DN from ipapython.ipa_log_manager import * from ipapython import certmonger from ipapython import dogtag @@ -322,6 +324,22 @@ def setup_firefox_extension(fstore): http.setup_firefox_extension(realm, domain) +def ca_configure_profiles_acl(ca): + root_logger.info('[Authorizing RA Agent to modify profiles]') + + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + if sysupgrade.get_upgrade_state('dogtag', 'agent_allow_profile_modify'): + return False + + cainstance.configure_profiles_acl() + + sysupgrade.set_upgrade_state('dogtag', 'agent_allow_profile_modifiy', True) + return True + + def upgrade_ipa_profile(ca, domain, fqdn): """ Update the IPA Profile provided by dogtag @@ -1420,6 +1438,7 @@ def main(): upgrade_ipa_profile(ca, api.env.domain, fqdn), certificate_renewal_update(ca), ca_enable_pkix(ca), + ca_configure_profiles_acl(ca), ]) if ca_restart: diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 030c9f12daba4b38b748da8940e38d3cf2109788..d85210e02a6b4a5e664d57965299d775836ae5c5 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -469,6 +469,7 @@ class CAInstance(DogtagInstance): self.step("requesting RA certificate from CA", self.__request_ra_certificate) self.step("issuing RA agent certificate", self.__issue_ra_cert) self.step("adding RA agent as a trusted user", self.__configure_ra) + self.step("authorizing RA to modify profiles", self.__configure_profiles_acl) self.step("configure certmonger for renewals", self.configure_certmonger_renewal) self.step("configure certificate renewals", self.configure_renewal) if not self.clone: @@ -940,6 +941,10 @@ class CAInstance(DogtagInstance): conn.unbind() + def __configure_profiles_acl(self): + """Allow the Certificate Manager Agents group to modify profiles.""" + configure_profiles_acl() + def __run_certutil(self, args, database=None, pwd_file=None, stdin=None): if not database: database = self.ra_agent_db @@ -1825,6 +1830,27 @@ def update_people_entry(dercert): return True +def configure_profiles_acl(): + server_id = installutils.realm_to_serverid(api.env.realm) + dogtag_uri = 'ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' % server_id + updated = False + + dn = DN(('cn', 'aclResources'), ('o', 'ipaca')) + rule = ( + 'certServer.profile.configuration:read,modify:allow (read,modify) ' + 'group="Certificate Manager Agents":' + 'Certificate Manager agents may modify (create/update/delete) and read profiles' + ) + modlist = [(ldap.MOD_ADD, 'resourceACLS', [rule])] + + conn = ldap2.ldap2(shared_instance=False, ldap_uri=dogtag_uri) + if not conn.isconnected(): + conn.connect(autobind=True) + rules = conn.get_entry(dn).get('resourceACLS', []) + if rule not in rules: + conn.conn.modify_s(str(dn), modlist) + conn.disconnect() + if __name__ == "__main__": standard_logging_setup("install.log") ds = dsinstance.DsInstance() -- 2.1.0
>From 7302ce777669ab4300c3919abe91565dd3cd1641 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 30 Apr 2015 04:55:29 -0400 Subject: [PATCH 05/11] Add certprofile plugin Add the 'certprofile' plugin which defines the commands for managing certificate profiles and associated permissions. Also update Dogtag network code in 'ipapython.dogtag' to support headers and arbitrary request bodies, to facilitate use of the Dogtag profiles REST API. Part of: https://fedorahosted.org/freeipa/ticket/57 --- ACI.txt | 8 ++ API.txt | 62 +++++++++ install/updates/40-certprofile.update | 9 ++ install/updates/40-delegation.update | 8 ++ install/updates/Makefile.am | 1 + ipalib/constants.py | 1 + ipalib/plugins/certprofile.py | 253 ++++++++++++++++++++++++++++++++++ ipapython/dogtag.py | 29 ++-- ipaserver/plugins/dogtag.py | 176 ++++++++++++++++++++++- 9 files changed, 534 insertions(+), 13 deletions(-) create mode 100644 install/updates/40-certprofile.update create mode 100644 ipalib/plugins/certprofile.py diff --git a/ACI.txt b/ACI.txt index bf539892910f14ebc3fbee88a72d2b57c0d1327b..870a343f0e59fa2075b53e881c224d4965984d08 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 0808f3c64595495c8a9e60da5cbd689d5cdc6224..55d2baccaaf7a6cd891aa872935c01c76781c273 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 0000000000000000000000000000000000000000..6b0a81d0ff6d69dabe82138227d105fc780ee17d --- /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 975929bd70400b2f9cf407d6faedb246003d7f58..bc0736c5b6c07747586a56c2cbde9596c7522d1c 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 0d63d9ea8d85f1add5f036e7a39f89543586d33b..cd44f08d4cd736ee19caf8ec1b8df604c9c2fa9d 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 \ 45-roles.update \ 50-7_bit_check.update \ diff --git a/ipalib/constants.py b/ipalib/constants.py index f1e14702ffdf5a3bd23a62b1fdd2ee3cd95d84f8..3722ab1999938ad021b8789645368633eac7f08b 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -116,6 +116,7 @@ DEFAULT_CONFIG = ( ('container_otp', DN(('cn', 'otp'))), ('container_radiusproxy', DN(('cn', 'radiusproxy'))), ('container_views', DN(('cn', 'views'), ('cn', 'accounts'))), + ('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 0000000000000000000000000000000000000000..3d9e807445f55b5406640a268ab0d50ddedc896d --- /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(self, *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(self, *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=(\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(self, *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(self, *args, **kwargs) diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py index c74b8736a4b15f7bf081206b52b9876a8c4928af..11311cf7b55d7b84e9434a698dbfd60b0eb142a1 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 52bdb0d4245594785e718c63242e27cee0e59322..9654123b16d8e417398d49bf1305fd41880bc3a7 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -4,8 +4,9 @@ # Jason Gerard DeRose <jder...@redhat.com> # Rob Crittenden <rcritten@@redhat.com> # John Dennis <jden...@redhat.com> +# Fraser Tweedale <ftwee...@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) -- 2.1.0
>From 3c9a29566e49b01f7017ef861daf1ca1967f59a7 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 11 May 2015 23:38:41 -0400 Subject: [PATCH 06/11] Enable LDAP-based profiles in CA on upgrade Part of: https://fedorahosted.org/freeipa/ticket/4560 --- install/tools/ipa-upgradeconfig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig index 92c4934874f12c7017329bdcb67ac9a888f6e389..246118fd451fbf57754049258aeaa1895eb11e5e 100755 --- a/install/tools/ipa-upgradeconfig +++ b/install/tools/ipa-upgradeconfig @@ -340,6 +340,45 @@ def ca_configure_profiles_acl(ca): return True +def ca_enable_ldap_profile_subsystem(ca): + root_logger.info('[Ensuring CA is using LDAPProfileSubsystem]') + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + caconfig = dogtag.configured_constants() + + needs_update = False + directive = None + try: + for i in range(15): + directive = "subsystem.{}.class".format(i) + value = installutils.get_directive( + caconfig.CS_CFG_PATH, + directive, + separator='=') + if value == 'ProfileSubsystem': + needs_update = True + break + except OSError, e: + root_logger.error('Cannot read CA configuration file "%s": %s', + caconfig.CS_CFG_PATH, e) + return False + + if needs_update: + installutils.set_directive( + caconfig.CS_CFG_PATH, + directive, + 'LDAPProfileSubsystem', + quotes=False, + separator='=') + + # TODO import file-based profiles into Dogtag + # More code needed on Dogtag side for this. + + return needs_update + + def upgrade_ipa_profile(ca, domain, fqdn): """ Update the IPA Profile provided by dogtag @@ -1439,6 +1478,7 @@ def main(): certificate_renewal_update(ca), ca_enable_pkix(ca), ca_configure_profiles_acl(ca), + ca_enable_ldap_profile_subsystem(ca), ]) if ca_restart: -- 2.1.0
>From 27ac261f1c67e150855ea1e94d5893138772364b Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 11 May 2015 21:17:48 -0400 Subject: [PATCH 07/11] Import included profiles during install or upgrade Add a default service profile template as part of FreeIPA and format and import it as part of installation or upgrade process. Also remove the code that modifies the old (file-based) `caIPAserviceCert' profile. Fixes https://fedorahosted.org/freeipa/ticket/4002 --- freeipa.spec.in | 2 + install/configure.ac | 1 + install/share/Makefile.am | 1 + install/share/profiles/DefaultService.cfg | 109 +++++++++++++++ install/share/profiles/Makefile.am | 14 ++ install/tools/ipa-server-install | 9 ++ install/tools/ipa-upgradeconfig | 49 ++++--- ipapython/dogtag.py | 5 + ipaserver/install/cainstance.py | 216 ++++-------------------------- ipaserver/plugins/dogtag.py | 14 +- 10 files changed, 201 insertions(+), 219 deletions(-) create mode 100644 install/share/profiles/DefaultService.cfg create mode 100644 install/share/profiles/Makefile.am diff --git a/freeipa.spec.in b/freeipa.spec.in index b14acee638d10d1e153e1f6765ab5902060cb169..6f5479240ba16a8a32ca2924437b4a2e7f37adc4 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -718,6 +718,8 @@ fi %dir %{_usr}/share/ipa/advise %dir %{_usr}/share/ipa/advise/legacy %{_usr}/share/ipa/advise/legacy/*.template +%dir %{_usr}/share/ipa/profiles +%{_usr}/share/ipa/profiles/*.cfg %dir %{_usr}/share/ipa/ffextension %{_usr}/share/ipa/ffextension/bootstrap.js %{_usr}/share/ipa/ffextension/install.rdf diff --git a/install/configure.ac b/install/configure.ac index 2e48aa5cc67b30f2582de987a12d4e7043256679..57f4219b66bbe1dadaed3e89c3e84b1c8240399e 100644 --- a/install/configure.ac +++ b/install/configure.ac @@ -88,6 +88,7 @@ AC_CONFIG_FILES([ share/Makefile share/advise/Makefile share/advise/legacy/Makefile + share/profiles/Makefile ui/Makefile ui/css/Makefile ui/src/Makefile diff --git a/install/share/Makefile.am b/install/share/Makefile.am index 2cae5279079cdd3e0d793667f4d1bf4e44757b9e..1ff2278ebf395057fccc4f0650b7726374cc1cc8 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -2,6 +2,7 @@ NULL = SUBDIRS = \ advise \ + profiles \ $(NULL) appdir = $(IPA_DATA_DIR) diff --git a/install/share/profiles/DefaultService.cfg b/install/share/profiles/DefaultService.cfg new file mode 100644 index 0000000000000000000000000000000000000000..d70fc9867d8af3f49dd6dfebcefdf772772bd100 --- /dev/null +++ b/install/share/profiles/DefaultService.cfg @@ -0,0 +1,109 @@ +profileId=DefaultService +classId=caEnrollImpl +desc=This certificate profile is for enrolling server certificates with IPA-RA agent authentication. +visible=false +enable=true +enableBy=admin +auth.instance_id=raCertAuth +name=IPA-RA Agent-Authenticated Server Certificate Enrollment +input.list=i1,i2 +input.i1.class_id=certReqInputImpl +input.i2.class_id=submitterInfoInputImpl +output.list=o1 +output.o1.class_id=certOutputImpl +policyset.list=serverCertSet +policyset.serverCertSet.list=1,2,3,4,5,6,7,8,9,10,11 +policyset.serverCertSet.1.constraint.class_id=subjectNameConstraintImpl +policyset.serverCertSet.1.constraint.name=Subject Name Constraint +policyset.serverCertSet.1.constraint.params.pattern=CN=[^,]+,.+ +policyset.serverCertSet.1.constraint.params.accept=true +policyset.serverCertSet.1.default.class_id=subjectNameDefaultImpl +policyset.serverCertSet.1.default.name=Subject Name Default +policyset.serverCertSet.1.default.params.name=CN=$$request.req_subject_name.cn$$, $SUBJECT_DN_O +policyset.serverCertSet.2.constraint.class_id=validityConstraintImpl +policyset.serverCertSet.2.constraint.name=Validity Constraint +policyset.serverCertSet.2.constraint.params.range=740 +policyset.serverCertSet.2.constraint.params.notBeforeCheck=false +policyset.serverCertSet.2.constraint.params.notAfterCheck=false +policyset.serverCertSet.2.default.class_id=validityDefaultImpl +policyset.serverCertSet.2.default.name=Validity Default +policyset.serverCertSet.2.default.params.range=731 +policyset.serverCertSet.2.default.params.startTime=0 +policyset.serverCertSet.3.constraint.class_id=keyConstraintImpl +policyset.serverCertSet.3.constraint.name=Key Constraint +policyset.serverCertSet.3.constraint.params.keyType=RSA +policyset.serverCertSet.3.constraint.params.keyParameters=1024,2048,3072,4096 +policyset.serverCertSet.3.default.class_id=userKeyDefaultImpl +policyset.serverCertSet.3.default.name=Key Default +policyset.serverCertSet.4.constraint.class_id=noConstraintImpl +policyset.serverCertSet.4.constraint.name=No Constraint +policyset.serverCertSet.4.default.class_id=authorityKeyIdentifierExtDefaultImpl +policyset.serverCertSet.4.default.name=Authority Key Identifier Default +policyset.serverCertSet.5.constraint.class_id=noConstraintImpl +policyset.serverCertSet.5.constraint.name=No Constraint +policyset.serverCertSet.5.default.class_id=authInfoAccessExtDefaultImpl +policyset.serverCertSet.5.default.name=AIA Extension Default +policyset.serverCertSet.5.default.params.authInfoAccessADEnable_0=true +policyset.serverCertSet.5.default.params.authInfoAccessADLocationType_0=URIName +policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0=http://$IPA_CA_RECORD.$DOMAIN/ca/ocsp +policyset.serverCertSet.5.default.params.authInfoAccessADMethod_0=1.3.6.1.5.5.7.48.1 +policyset.serverCertSet.5.default.params.authInfoAccessCritical=false +policyset.serverCertSet.5.default.params.authInfoAccessNumADs=1 +policyset.serverCertSet.6.constraint.class_id=keyUsageExtConstraintImpl +policyset.serverCertSet.6.constraint.name=Key Usage Extension Constraint +policyset.serverCertSet.6.constraint.params.keyUsageCritical=true +policyset.serverCertSet.6.constraint.params.keyUsageDigitalSignature=true +policyset.serverCertSet.6.constraint.params.keyUsageNonRepudiation=true +policyset.serverCertSet.6.constraint.params.keyUsageDataEncipherment=true +policyset.serverCertSet.6.constraint.params.keyUsageKeyEncipherment=true +policyset.serverCertSet.6.constraint.params.keyUsageKeyAgreement=false +policyset.serverCertSet.6.constraint.params.keyUsageKeyCertSign=false +policyset.serverCertSet.6.constraint.params.keyUsageCrlSign=false +policyset.serverCertSet.6.constraint.params.keyUsageEncipherOnly=false +policyset.serverCertSet.6.constraint.params.keyUsageDecipherOnly=false +policyset.serverCertSet.6.default.class_id=keyUsageExtDefaultImpl +policyset.serverCertSet.6.default.name=Key Usage Default +policyset.serverCertSet.6.default.params.keyUsageCritical=true +policyset.serverCertSet.6.default.params.keyUsageDigitalSignature=true +policyset.serverCertSet.6.default.params.keyUsageNonRepudiation=true +policyset.serverCertSet.6.default.params.keyUsageDataEncipherment=true +policyset.serverCertSet.6.default.params.keyUsageKeyEncipherment=true +policyset.serverCertSet.6.default.params.keyUsageKeyAgreement=false +policyset.serverCertSet.6.default.params.keyUsageKeyCertSign=false +policyset.serverCertSet.6.default.params.keyUsageCrlSign=false +policyset.serverCertSet.6.default.params.keyUsageEncipherOnly=false +policyset.serverCertSet.6.default.params.keyUsageDecipherOnly=false +policyset.serverCertSet.7.constraint.class_id=noConstraintImpl +policyset.serverCertSet.7.constraint.name=No Constraint +policyset.serverCertSet.7.default.class_id=extendedKeyUsageExtDefaultImpl +policyset.serverCertSet.7.default.name=Extended Key Usage Extension Default +policyset.serverCertSet.7.default.params.exKeyUsageCritical=false +policyset.serverCertSet.7.default.params.exKeyUsageOIDs=1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2 +policyset.serverCertSet.8.constraint.class_id=signingAlgConstraintImpl +policyset.serverCertSet.8.constraint.name=No Constraint +policyset.serverCertSet.8.constraint.params.signingAlgsAllowed=SHA1withRSA,SHA256withRSA,SHA512withRSA,MD5withRSA,MD2withRSA,SHA1withDSA,SHA1withEC,SHA256withEC,SHA384withEC,SHA512withEC +policyset.serverCertSet.8.default.class_id=signingAlgDefaultImpl +policyset.serverCertSet.8.default.name=Signing Alg +policyset.serverCertSet.8.default.params.signingAlg=- +policyset.serverCertSet.9.constraint.class_id=noConstraintImpl +policyset.serverCertSet.9.constraint.name=No Constraint +policyset.serverCertSet.9.default.class_id=crlDistributionPointsExtDefaultImpl +policyset.serverCertSet.9.default.name=CRL Distribution Points Extension Default +policyset.serverCertSet.9.default.params.crlDistPointsCritical=false +policyset.serverCertSet.9.default.params.crlDistPointsNum=1 +policyset.serverCertSet.9.default.params.crlDistPointsEnable_0=true +policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0=$CRL_ISSUER +policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0=DirectoryName +policyset.serverCertSet.9.default.params.crlDistPointsPointName_0=http://$IPA_CA_RECORD.$DOMAIN/ipa/crl/MasterCRL.bin +policyset.serverCertSet.9.default.params.crlDistPointsPointType_0=URIName +policyset.serverCertSet.9.default.params.crlDistPointsReasons_0= +policyset.serverCertSet.10.constraint.class_id=noConstraintImpl +policyset.serverCertSet.10.constraint.name=No Constraint +policyset.serverCertSet.10.default.class_id=subjectKeyIdentifierExtDefaultImpl +policyset.serverCertSet.10.default.name=Subject Key Identifier Extension Default +policyset.serverCertSet.10.default.params.critical=false +policyset.serverCertSet.11.constraint.class_id=noConstraintImpl +policyset.serverCertSet.11.constraint.name=No Constraint +policyset.serverCertSet.11.default.class_id=userExtensionDefaultImpl +policyset.serverCertSet.11.default.name=User Supplied Extension Default +policyset.serverCertSet.11.default.params.userExtOID=2.5.29.17 diff --git a/install/share/profiles/Makefile.am b/install/share/profiles/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..8c6b3114a346b3d96d94e75ad330c5f0bbaf7322 --- /dev/null +++ b/install/share/profiles/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +appdir = $(IPA_DATA_DIR)/profiles +app_DATA = \ + DefaultService.cfg \ + $(NULL) + +EXTRA_DIST = \ + $(app_DATA) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install index cb6e1abe2016c0f8cefc35b1d685373f05b3ef89..7a7743e17c0f3ad9215dfc9d985d0ff021e57277 100755 --- a/install/tools/ipa-server-install +++ b/install/tools/ipa-server-install @@ -1124,6 +1124,9 @@ def main(): api.env.ca_host = host_name api.bootstrap(**cfg) + if setup_ca: + # ensure profile backend is available + import ipaserver.plugins.dogtag api.finalize() # Create DS user/group if it doesn't exist yet @@ -1273,6 +1276,12 @@ def main(): service.print_msg("Restarting the certificate server") ca.restart(dogtag.configured_constants().PKI_INSTANCE_NAME) + service.print_msg("Importing certificate profiles") + api.Backend.ra_certprofile._read_password() + if not api.Backend.ldap2.isconnected(): + api.Backend.ldap2.connect(autobind=True) + ca.import_included_profiles() + if options.setup_dns: api.Backend.ldap2.connect(autobind=True) dns_installer.install(False, False, options) diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig index 246118fd451fbf57754049258aeaa1895eb11e5e..24cdf466ceff3885b779b7de88681c087792c99e 100755 --- a/install/tools/ipa-upgradeconfig +++ b/install/tools/ipa-upgradeconfig @@ -379,32 +379,34 @@ def ca_enable_ldap_profile_subsystem(ca): return needs_update -def upgrade_ipa_profile(ca, domain, fqdn): +def ca_import_included_profiles(ca): + root_logger.info('[Ensuring presence of included profiles]') + + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + if not api.Backend.ldap2.isconnected(): + try: + api.Backend.ldap2.connect(autobind=True) + except ipalib.errors.PublicError, e: + root_logger.error("Cannot connect to LDAP: %s", e) + return + ca.import_included_profiles() + + +def upgrade_ca_audit_cert_validity(ca): """ - Update the IPA Profile provided by dogtag + Update the Dogtag audit signing certificate. Returns True if restart is needed, False otherwise. """ - root_logger.info('[Verifying that CA service certificate profile is updated]') + root_logger.info('[Verifying that CA audit signing cert has 2 year validity]') if ca.is_configured(): - ski = ca.enable_subject_key_identifier() - if ski: - root_logger.debug('Subject Key Identifier updated.') - else: - root_logger.debug('Subject Key Identifier already set.') - san = ca.enable_subject_alternative_name() - if san: - root_logger.debug('Subject Alternative Name updated.') - else: - root_logger.debug('Subject Alternative Name already set.') - audit = ca.set_audit_renewal() - uri = ca.set_crl_ocsp_extensions(domain, fqdn) - if audit or ski or san or uri: - return True + return ca.set_audit_renewal() else: root_logger.info('CA is not configured') - - return False + return False def named_remove_deprecated_options(): @@ -1327,6 +1329,7 @@ def main(): fstore = sysrestore.FileStore(paths.SYSRESTORE) api.bootstrap(context='restart', in_server=True) + import ipaserver.plugins.dogtag # ensure profile backend gets loaded api.finalize() fqdn = find_hostname() @@ -1474,7 +1477,7 @@ def main(): ca_restart = any([ ca_restart, - upgrade_ipa_profile(ca, api.env.domain, fqdn), + upgrade_ca_audit_cert_validity(ca), certificate_renewal_update(ca), ca_enable_pkix(ca), ca_configure_profiles_acl(ca), @@ -1488,6 +1491,12 @@ def main(): except ipautil.CalledProcessError, e: root_logger.error("Failed to restart %s: %s", ca.service_name, e) + # This step MUST be done after ca_enable_ldap_profile_subsystem and + # ca_configure_profiles_acl, and the consequent restart, but does not + # itself require a restart. + # + ca_import_included_profiles(ca) + set_sssd_domain_option('ipa_server_mode', 'True') if __name__ == '__main__': diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py index 11311cf7b55d7b84e9434a698dbfd60b0eb142a1..e0091ba8747dff3a488b9908f057ae15c6b4bedc 100644 --- a/ipapython/dogtag.py +++ b/ipapython/dogtag.py @@ -42,6 +42,11 @@ from ipapython.ipa_log_manager import * # the configured version. +INCLUDED_PROFILES = { + # ( profile_id , description , store_issued) + (u'DefaultService', u'Standard profile for network services', True), + } + class Dogtag10Constants(object): DOGTAG_VERSION = 10 UNSECURE_PORT = 8080 diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index d85210e02a6b4a5e664d57965299d775836ae5c5..7fc1572604a09cc1c6bc28f7a068b5b2d49a6a12 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -459,10 +459,6 @@ class CAInstance(DogtagInstance): self.step("importing CA chain to RA certificate database", self.__import_ca_chain) self.step("fixing RA database permissions", self.fix_ra_perms) self.step("setting up signing cert profile", self.__setup_sign_profile) - self.step("set certificate subject base", self.__set_subject_in_config) - self.step("enabling Subject Key Identifier", self.enable_subject_key_identifier) - self.step("enabling Subject Alternative Name", self.enable_subject_alternative_name) - self.step("enabling CRL and OCSP extensions for certificates", self.__set_crl_ocsp_extensions) self.step("setting audit signing renewal to 2 years", self.set_audit_renewal) if not self.clone: self.step("restarting certificate server", self.restart_instance) @@ -1125,94 +1121,6 @@ class CAInstance(DogtagInstance): return publishdir - def __set_crl_ocsp_extensions(self): - self.set_crl_ocsp_extensions(self.domain, self.fqdn) - - def set_crl_ocsp_extensions(self, domain, fqdn): - """ - Configure CRL and OCSP extensions in default IPA certificate profile - if not done already. - """ - changed = False - - # OCSP extension - ocsp_url = 'http://%s.%s/ca/ocsp' % (IPA_CA_RECORD, ipautil.format_netloc(domain)) - - ocsp_location_0 = installutils.get_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0', - separator='=') - - if ocsp_location_0 != ocsp_url: - # Set the first OCSP URI - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.5.default.params.authInfoAccessADLocation_0', - ocsp_url, quotes=False, separator='=') - changed = True - - ocsp_profile_count = installutils.get_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.5.default.params.authInfoAccessNumADs', - separator='=') - - if ocsp_profile_count != '1': - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.5.default.params.authInfoAccessNumADs', - '1', quotes=False, separator='=') - changed = True - - - # CRL extension - crl_url = 'http://%s.%s/ipa/crl/MasterCRL.bin'% (IPA_CA_RECORD, ipautil.format_netloc(domain)) - - crl_point_0 = installutils.get_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.9.default.params.crlDistPointsPointName_0', - separator='=') - - if crl_point_0 != crl_url: - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.9.default.params.crlDistPointsIssuerName_0', - 'CN=Certificate Authority,o=ipaca', quotes=False, separator='=') - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.9.default.params.crlDistPointsIssuerType_0', - 'DirectoryName', quotes=False, separator='=') - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.9.default.params.crlDistPointsPointName_0', - crl_url, quotes=False, separator='=') - changed = True - - crl_profile_count = installutils.get_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.9.default.params.crlDistPointsNum', - separator='=') - - if crl_profile_count != '1': - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.9.default.params.crlDistPointsNum', - '1', quotes=False, separator='=') - changed = True - - # CRL extension is not enabled by default - setlist = installutils.get_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.list', separator='=') - new_set_list = None - - if setlist == '1,2,3,4,5,6,7,8': - new_set_list = '1,2,3,4,5,6,7,8,9' - elif setlist == '1,2,3,4,5,6,7,8,10': - new_set_list = '1,2,3,4,5,6,7,8,9,10' - elif setlist == '1,2,3,4,5,6,7,8,10,11': - new_set_list = '1,2,3,4,5,6,7,8,9,10,11' - - if new_set_list: - installutils.set_directive(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.list', - new_set_list, quotes=False, separator='=') - changed = True - - return changed - def __enable_crl_publish(self): """ @@ -1267,13 +1175,6 @@ class CAInstance(DogtagInstance): installutils.set_directive(caconfig, 'ca.crl.MasterCRL.enableCRLUpdates', 'false', quotes=False, separator='=') installutils.set_directive(caconfig, 'ca.listenToCloneModifications', 'false', quotes=False, separator='=') - def __set_subject_in_config(self): - # dogtag ships with an IPA-specific profile that forces a subject - # format. We need to update that template with our base subject - if installutils.update_file(self.dogtag_constants.IPA_SERVICE_PROFILE, - 'OU=pki-ipa, O=IPA', str(self.subject_base)): - print "Updating subject_base in CA template failed" - def uninstall(self): # just eat state self.restore_state("enabled") @@ -1407,100 +1308,6 @@ class CAInstance(DogtagInstance): services.knownservices.certmonger.stop() - def enable_subject_key_identifier(self): - """ - See if Subject Key Identifier is set in the profile and if not, add it. - """ - setlist = installutils.get_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.list', separator='=') - - # this is the default setting from pki-ca/pki-tomcat. Don't touch it - # if a user has manually modified it. - if setlist == '1,2,3,4,5,6,7,8' or setlist == '1,2,3,4,5,6,7,8,9': - setlist += ',10' - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.list', - setlist, - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.10.constraint.class_id', - 'noConstraintImpl', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.10.constraint.name', - 'No Constraint', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.10.default.class_id', - 'subjectKeyIdentifierExtDefaultImpl', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.10.default.name', - 'Subject Key Identifier Extension Default', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.10.default.params.critical', - 'false', - quotes=False, separator='=') - return True - - # No update was done - return False - - def enable_subject_alternative_name(self): - """ - See if Subject Alternative Name is set in the profile and if not, add - it. - """ - setlist = installutils.get_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.list', separator='=') - - # this is the default setting from pki-ca/pki-tomcat. Don't touch it - # if a user has manually modified it. - if setlist == '1,2,3,4,5,6,7,8,10' or setlist == '1,2,3,4,5,6,7,8,9,10': - setlist += ',11' - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.list', - setlist, - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.11.constraint.class_id', - 'noConstraintImpl', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.11.constraint.name', - 'No Constraint', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.11.default.class_id', - 'userExtensionDefaultImpl', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.11.default.name', - 'User Supplied Extension Default', - quotes=False, separator='=') - installutils.set_directive( - self.dogtag_constants.IPA_SERVICE_PROFILE, - 'policyset.serverCertSet.11.default.params.userExtOID', - '2.5.29.17', - quotes=False, separator='=') - return True - - # No update was done - return False def set_audit_renewal(self): """ @@ -1586,6 +1393,29 @@ class CAInstance(DogtagInstance): master_entry['ipaConfigString'].append('caRenewalMaster') self.admin_conn.update_entry(master_entry) + def import_included_profiles(self): + sub_dict = dict( + DOMAIN=ipautil.format_netloc(api.env.domain), + IPA_CA_RECORD=IPA_CA_RECORD, + CRL_ISSUER='CN=Certificate Authority,o=ipaca', + SUBJECT_DN_O=str(DN(('O', api.env.realm))), + ) + + for (profile_id, desc, store_issued) in dogtag.INCLUDED_PROFILES: + try: + show_ret = api.Command['certprofile_show'](profile_id) + continue # the profile is present + except errors.NotFound: + # profile not found; add it + profile_data = ipautil.template_file( + '/usr/share/ipa/profiles/{}.cfg'.format(profile_id), sub_dict) + api.Command['certprofile_import']( + profile_id, + file=profile_data.decode('utf-8'), + description=desc, + ipacertprofilestoreissued=store_issued, + ) + root_logger.info("Imported profile '%s'", profile_id) @staticmethod def update_cert_config(nickname, cert, dogtag_constants=None): diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 9654123b16d8e417398d49bf1305fd41880bc3a7..880b319d68728a40f4479626d5a7c2b8c56ced02 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -1966,17 +1966,19 @@ class RestClient(Backend): 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 = '' + self._read_password() super(RestClient, self).__init__() # session cookie self.cookie = None + def _read_password(self): + try: + with open(self.pwd_file) as f: + self.password = f.readline().strip() + except IOError: + self.password = '' + @cachedproperty def ca_host(self): """ -- 2.1.0
>From 28e8d503b7d5f13a40ca177f15a69696042a996f Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 7 May 2015 21:26:24 -0400 Subject: [PATCH 08/11] Add generic split_any_principal method There exist methods to split user or service/host principals, but there is no method to split any kind of principal and allow the caller to decide what to do. Generalize ``ipalib.plugins.service.split_principal`` to return a service of ``None`` if the principal is a user principal, rename it ``split_any_principal`` and reimplement ``split_principal`` to preserve existing behaviour. Part of: https://fedorahosted.org/freeipa/ticket/4938 --- ipalib/plugins/service.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index b37dc7b4bf56b69df204fd29e9487f1390197bbe..5af320286bab98535e1f7118840afc4d525be401 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -185,19 +185,24 @@ _ticket_flags_map = { _ticket_flags_default = _ticket_flags_map['ipakrbrequirespreauth'] -def split_principal(principal): +def split_any_principal(principal): service = hostname = realm = None # Break down the principal into its component parts, which may or # may not include the realm. sp = principal.split('/') - if len(sp) != 2: - raise errors.MalformedServicePrincipal(reason=_('missing service')) + name_and_realm = None + if len(sp) > 2: + raise errors.MalformedServicePrincipal(reason=_('unable to determine service')) + elif len(sp) == 2: + service = sp[0] + if len(service) == 0: + raise errors.MalformedServicePrincipal(reason=_('blank service')) + name_and_realm = sp[1] + else: + name_and_realm = sp[0] - service = sp[0] - if len(service) == 0: - raise errors.MalformedServicePrincipal(reason=_('blank service')) - sr = sp[1].split('@') + sr = name_and_realm.split('@') if len(sr) > 2: raise errors.MalformedServicePrincipal( reason=_('unable to determine realm')) @@ -212,7 +217,13 @@ def split_principal(principal): realm = api.env.realm # Note that realm may be None. - return (service, hostname, realm) + return service, hostname, realm + +def split_principal(principal): + service, name, realm = split_any_principal(principal) + if service is None: + raise errors.MalformedServicePrincipal(reason=_('missing service')) + return service, name, realm def validate_principal(ugettext, principal): (service, hostname, principal) = split_principal(principal) -- 2.1.0
>From 8c3267f2d1026835ace648c0a66566ed1a5dda06 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Fri, 8 May 2015 02:23:24 -0400 Subject: [PATCH 09/11] Add profile_id parameter to 'request_certificate' Add the profile_id parameter to the 'request_certificate' function and update call sites. Also remove multiple occurrences of the default profile ID 'caIPAserviceCert'. Part of: https://fedorahosted.org/freeipa/ticket/57 --- API.txt | 3 ++- checks/check-ra.py | 2 +- ipalib/plugins/cert.py | 2 +- ipapython/dogtag.py | 6 ++++-- ipaserver/install/certs.py | 2 +- ipaserver/plugins/dogtag.py | 7 +++++-- ipaserver/plugins/rabase.py | 3 ++- 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/API.txt b/API.txt index 55d2baccaaf7a6cd891aa872935c01c76781c273..93ef2cdae533ad5564685bb21c8857a807dc8e08 100644 --- a/API.txt +++ b/API.txt @@ -485,10 +485,11 @@ arg: Str('serial_number') option: Str('version?', exclude='webui') output: Output('result', None, None) command: cert_request -args: 1,4,1 +args: 1,5,1 arg: File('csr', cli_name='csr_file') option: Flag('add', autofill=True, default=False) option: Str('principal') +option: Str('profile_id') option: Str('request_type', autofill=True, default=u'pkcs10') option: Str('version?', exclude='webui') output: Output('result', <type 'dict'>, None) diff --git a/checks/check-ra.py b/checks/check-ra.py index a1df50ba4a4ad7fc0b6d2118e40977b1da6edf65..28929545ab7f0a63e47a3829c53cf08d784c9524 100755 --- a/checks/check-ra.py +++ b/checks/check-ra.py @@ -90,7 +90,7 @@ def assert_equal(trial, reference): api.log.info('******** Testing ra.request_certificate() ********') -request_result = ra.request_certificate(csr) +request_result = ra.request_certificate(csr, ra.DEFAULT_PROFILE) if verbose: print "request_result=\n%s" % request_result assert_equal(request_result, {'subject' : subject, diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index 7e2c77622b3627e9e57bbcb69291f723ecf509bf..e4cb6dc0aa8b89368806b08674aae277b3653e8f 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -436,7 +436,7 @@ class cert_request(VirtualCommand): # Request the certificate result = self.Backend.ra.request_certificate( - csr, request_type=request_type) + csr, 'caIPAserviceCert', request_type=request_type) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py index e0091ba8747dff3a488b9908f057ae15c6b4bedc..6476d7e5813967cb1b812dc8a01eb3a0fae96d4e 100644 --- a/ipapython/dogtag.py +++ b/ipapython/dogtag.py @@ -47,6 +47,8 @@ INCLUDED_PROFILES = { (u'DefaultService', u'Standard profile for network services', True), } +DEFAULT_PROFILE = 'caIPAserviceCert' + class Dogtag10Constants(object): DOGTAG_VERSION = 10 UNSECURE_PORT = 8080 @@ -76,7 +78,7 @@ class Dogtag10Constants(object): RACERT_LINE_SEP = '\n' - IPA_SERVICE_PROFILE = '%s/caIPAserviceCert.cfg' % SERVICE_PROFILE_DIR + IPA_SERVICE_PROFILE = '%s/%s.cfg' % (SERVICE_PROFILE_DIR, DEFAULT_PROFILE) SIGN_PROFILE = '%s/caJarSigningCert.cfg' % SERVICE_PROFILE_DIR SHARED_DB = True DS_USER = "dirsrv" @@ -115,7 +117,7 @@ class Dogtag9Constants(object): EE_CLIENT_AUTH_PORT = 9446 TOMCAT_SERVER_PORT = 9701 - IPA_SERVICE_PROFILE = '%s/caIPAserviceCert.cfg' % SERVICE_PROFILE_DIR + IPA_SERVICE_PROFILE = '%s/%s.cfg' % (SERVICE_PROFILE_DIR, DEFAULT_PROFILE) SIGN_PROFILE = '%s/caJarSigningCert.cfg' % SERVICE_PROFILE_DIR SHARED_DB = False DS_USER = "pkisrv" diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index bc7dccf805386e9fa84b58d2ff9346085e1b93b1..564332e6fde0698a23884922c5018fab59da7e4d 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -386,7 +386,7 @@ class CertDB(object): # We just want the CSR bits, make sure there is nothing else csr = pkcs10.strip_header(csr) - params = {'profileId': 'caIPAserviceCert', + params = {'profileId': dogtag.DEFAULT_PROFILE, 'cert_request_type': 'pkcs10', 'requestor_name': 'IPA Installer', 'cert_request': csr, diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 880b319d68728a40f4479626d5a7c2b8c56ced02..e6668bb43b994863a14fdd347635753422ed9388 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -1284,6 +1284,8 @@ class ra(rabase.rabase): """ Request Authority backend plugin. """ + DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE + def __init__(self): if api.env.in_tree: self.sec_dir = api.env.dot_ipa + os.sep + 'alias' @@ -1541,9 +1543,10 @@ class ra(rabase.rabase): return cmd_result - def request_certificate(self, csr, request_type='pkcs10'): + def request_certificate(self, csr, profile_id, request_type='pkcs10'): """ :param csr: The certificate signing request. + :param profile_id: The profile to use for the request. :param request_type: The request type (defaults to ``'pkcs10'``). Submit certificate signing request. @@ -1575,7 +1578,7 @@ class ra(rabase.rabase): http_status, http_reason_phrase, http_headers, http_body = \ self._sslget('/ca/eeca/ca/profileSubmitSSLClient', self.env.ca_ee_port, - profileId='caIPAserviceCert', + profileId=profile_id, cert_request_type=request_type, cert_request=csr, xml='true') diff --git a/ipaserver/plugins/rabase.py b/ipaserver/plugins/rabase.py index e14969970ef5b402d06b766f895200c6eb4fc76f..cf4426235b02866a3f565c51c52c44aabbdc1153 100644 --- a/ipaserver/plugins/rabase.py +++ b/ipaserver/plugins/rabase.py @@ -67,11 +67,12 @@ class rabase(Backend): """ raise errors.NotImplementedError(name='%s.get_certificate' % self.name) - def request_certificate(self, csr, request_type='pkcs10'): + def request_certificate(self, csr, profile_id, request_type='pkcs10'): """ Submit certificate signing request. :param csr: The certificate signing request. + :param profile_id: Profile to use for this request. :param request_type: The request type (defaults to ``'pkcs10'``). """ raise errors.NotImplementedError(name='%s.request_certificate' % self.name) -- 2.1.0
>From 9bf0edfbb1e5458e92c76309c58fade50b9fc0d0 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 18 May 2015 22:11:52 -0400 Subject: [PATCH 10/11] Add usercertificate attribute to user plugin --- ACI.txt | 2 +- API.txt | 18 ++++++++++++------ install/share/default-aci.ldif | 1 + install/updates/20-aci.update | 4 ++++ ipalib/plugins/baseuser.py | 10 ++++++++-- ipalib/plugins/user.py | 4 ++-- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/ACI.txt b/ACI.txt index 870a343f0e59fa2075b53e881c224d4965984d08..279d15d6dcf2a465a07ec27a67b15bf452da6c81 100644 --- a/ACI.txt +++ b/ACI.txt @@ -281,7 +281,7 @@ aci: (targetattr = "krbprincipalkey || passwordhistory || sambalmpassword || sam dn: cn=users,cn=accounts,dc=ipa,dc=example aci: (targetattr = "ipasshpubkey")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Manage User SSH Public Keys";allow (write) groupdn = "ldap:///cn=System: Manage User SSH Public Keys,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "businesscategory || carlicense || cn || description || displayname || employeetype || facsimiletelephonenumber || gecos || givenname || homephone || inetuserhttpurl || initials || l || labeleduri || loginshell || manager || mepmanagedentry || mobile || objectclass || ou || pager || postalcode || preferredlanguage || roomnumber || secretary || seealso || sn || st || street || telephonenumber || title || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Modify Users";allow (write) groupdn = "ldap:///cn=System: Modify Users,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "businesscategory || carlicense || cn || description || displayname || employeetype || facsimiletelephonenumber || gecos || givenname || homephone || inetuserhttpurl || initials || l || labeleduri || loginshell || manager || mepmanagedentry || mobile || objectclass || ou || pager || postalcode || preferredlanguage || roomnumber || secretary || seealso || sn || st || street || telephonenumber || title || usercertificate || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Modify Users";allow (write) groupdn = "ldap:///cn=System: Modify Users,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example aci: (targetattr = "*")(target = "ldap:///cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read UPG Definition";allow (compare,read,search) groupdn = "ldap:///cn=System: Read UPG Definition,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example diff --git a/API.txt b/API.txt index 93ef2cdae533ad5564685bb21c8857a807dc8e08..a1ff2afbd1d29087c4295bf9b58b596f73279c27 100644 --- a/API.txt +++ b/API.txt @@ -3772,7 +3772,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: PrimaryKey('value', None, None) command: stageuser_add -args: 1,43,3 +args: 1,44,3 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 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') @@ -3814,6 +3814,7 @@ option: Str('street', attribute=True, cli_name='street', multivalue=False, requi option: Str('telephonenumber', attribute=True, cli_name='phone', multivalue=True, required=False) option: Str('title', attribute=True, cli_name='title', multivalue=False, required=False) option: Int('uidnumber', attribute=True, cli_name='uid', minvalue=1, multivalue=False, required=False) +option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=False, required=False) option: Str('userclass', attribute=True, cli_name='class', multivalue=True, required=False) option: Password('userpassword', attribute=True, cli_name='password', exclude='webui', multivalue=False, required=False) option: Str('version?', exclude='webui') @@ -3829,7 +3830,7 @@ output: Output('result', <type 'dict'>, None) output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: ListOfPrimaryKeys('value', None, None) command: stageuser_find -args: 1,52,4 +args: 1,53,4 arg: Str('criteria?', noextrawhitespace=False) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') option: Str('carlicense', attribute=True, autofill=False, cli_name='carlicense', multivalue=True, query=True, required=False) @@ -3880,6 +3881,7 @@ option: Int('timelimit?', autofill=False, minvalue=0) option: Str('title', attribute=True, autofill=False, cli_name='title', multivalue=False, query=True, required=False) option: Str('uid', attribute=True, autofill=False, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, query=True, required=False) option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, query=True, required=False) +option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, query=True, required=False) option: Str('userclass', attribute=True, autofill=False, cli_name='class', multivalue=True, query=True, required=False) option: Password('userpassword', attribute=True, autofill=False, cli_name='password', exclude='webui', multivalue=False, query=True, required=False) option: Str('version?', exclude='webui') @@ -3888,7 +3890,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: Output('truncated', <type 'bool'>, None) command: stageuser_mod -args: 1,44,3 +args: 1,45,3 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 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') @@ -3931,6 +3933,7 @@ option: Str('street', attribute=True, autofill=False, cli_name='street', multiva option: Str('telephonenumber', attribute=True, autofill=False, cli_name='phone', multivalue=True, required=False) option: Str('title', attribute=True, autofill=False, cli_name='title', multivalue=False, required=False) option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, required=False) +option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, required=False) option: Str('userclass', attribute=True, autofill=False, cli_name='class', multivalue=True, required=False) option: Password('userpassword', attribute=True, autofill=False, cli_name='password', exclude='webui', multivalue=False, required=False) option: Str('version?', exclude='webui') @@ -4558,7 +4561,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: PrimaryKey('value', None, None) command: user_add -args: 1,44,3 +args: 1,45,3 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 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') @@ -4601,6 +4604,7 @@ option: Str('street', attribute=True, cli_name='street', multivalue=False, requi option: Str('telephonenumber', attribute=True, cli_name='phone', multivalue=True, required=False) option: Str('title', attribute=True, cli_name='title', multivalue=False, required=False) option: Int('uidnumber', attribute=True, cli_name='uid', minvalue=1, multivalue=False, required=False) +option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=False, required=False) option: Str('userclass', attribute=True, cli_name='class', multivalue=True, required=False) option: Password('userpassword', attribute=True, cli_name='password', exclude='webui', multivalue=False, required=False) option: Str('version?', exclude='webui') @@ -4632,7 +4636,7 @@ output: Output('result', <type 'bool'>, None) output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: PrimaryKey('value', None, None) command: user_find -args: 1,55,4 +args: 1,56,4 arg: Str('criteria?', noextrawhitespace=False) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') option: Str('carlicense', attribute=True, autofill=False, cli_name='carlicense', multivalue=True, query=True, required=False) @@ -4685,6 +4689,7 @@ option: Int('timelimit?', autofill=False, minvalue=0) option: Str('title', attribute=True, autofill=False, cli_name='title', multivalue=False, query=True, required=False) option: Str('uid', attribute=True, autofill=False, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, query=True, required=False) option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, query=True, required=False) +option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, query=True, required=False) option: Str('userclass', attribute=True, autofill=False, cli_name='class', multivalue=True, query=True, required=False) option: Password('userpassword', attribute=True, autofill=False, cli_name='password', exclude='webui', multivalue=False, query=True, required=False) option: Str('version?', exclude='webui') @@ -4694,7 +4699,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: Output('truncated', <type 'bool'>, None) command: user_mod -args: 1,45,3 +args: 1,46,3 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', 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') @@ -4738,6 +4743,7 @@ option: Str('street', attribute=True, autofill=False, cli_name='street', multiva option: Str('telephonenumber', attribute=True, autofill=False, cli_name='phone', multivalue=True, required=False) option: Str('title', attribute=True, autofill=False, cli_name='title', multivalue=False, required=False) option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, required=False) +option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, required=False) option: Str('userclass', attribute=True, autofill=False, cli_name='class', multivalue=True, required=False) option: Password('userpassword', attribute=True, autofill=False, cli_name='password', exclude='webui', multivalue=False, required=False) option: Str('version?', exclude='webui') diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif index af7eedb0b92375f893a61ad1fb6e2d7b176389f9..7b174e774aae3ea012a431fe4a2535fb4230e402 100644 --- a/install/share/default-aci.ldif +++ b/install/share/default-aci.ldif @@ -10,6 +10,7 @@ changetype: modify add: aci aci: (targetattr = "givenname || sn || cn || displayname || title || initials || loginshell || gecos || homephone || mobile || pager || facsimiletelephonenumber || telephonenumber || street || roomnumber || l || st || postalcode || manager || secretary || description || carlicense || labeleduri || inetuserhttpurl || seealso || employeetype || businesscategory || ou")(version 3.0;acl "selfservice:User Self service";allow (write) userdn = "ldap:///self";) aci: (targetattr = "ipasshpubkey")(version 3.0;acl "selfservice:Users can manage their own SSH public keys";allow (write) userdn = "ldap:///self";) +aci: (targetattr = "usercertificate")(version 3.0;acl "selfservice:Users can manage their own X.509 certificates";allow (write) userdn = "ldap:///self";) dn: cn=etc,$SUFFIX changetype: modify diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update index fde3afeee59e4d4dc0bd6a9c0eb24ab255c4e637..8964876b0f07c664e4855baa3971a2c774073f09 100644 --- a/install/updates/20-aci.update +++ b/install/updates/20-aci.update @@ -79,3 +79,7 @@ add:aci: (targetattr="ipaProtectedOperation;write_keys")(version 3.0; acl "Group add:aci: (targetattr="ipaProtectedOperation;write_keys")(version 3.0; acl "Entities are allowed to rekey themselves"; allow(write) userdn="ldap:///self";) add:aci: (targetattr="ipaProtectedOperation;write_keys")(version 3.0; acl "Admins are allowed to rekey any entity"; allow(write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) add:aci: (targetfilter="(|(objectclass=ipaHost)(objectclass=ipaService))")(targetattr="ipaProtectedOperation;write_keys")(version 3.0; acl "Entities are allowed to rekey managed entries"; allow(write) userattr="managedby#USERDN";) + +# User certificates +dn: $SUFFIX +add:aci:(targetattr = "ipasshpubkey")(version 3.0;acl "selfservice:Users can manage their own X.509 certificates";allow (write) userdn = "ldap:///self";) diff --git a/ipalib/plugins/baseuser.py b/ipalib/plugins/baseuser.py index a1be29d83550a0412ed37cfde47ac74c6ca478fd..5004cbc12f0eaecfffa0d14e8367a5c5ca4a70fd 100644 --- a/ipalib/plugins/baseuser.py +++ b/ipalib/plugins/baseuser.py @@ -23,10 +23,11 @@ import posixpath import os from ipalib import api, errors -from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime +from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes from ipalib.plugable import Registry from ipalib.plugins.baseldap import DN, LDAPObject, \ LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete, LDAPRetrieve +from ipalib.plugins.service import validate_certificate from ipalib.plugins import baseldap from ipalib.request import context from ipalib import _, ngettext @@ -188,7 +189,7 @@ class baseuser(LDAPObject): 'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'memberofindirect', 'ipauserauthtype', 'userclass', 'ipatokenradiusconfiglink', 'ipatokenradiususername', - 'krbprincipalexpiration' + 'krbprincipalexpiration', 'usercertificate', ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', @@ -383,6 +384,11 @@ class baseuser(LDAPObject): + '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$', pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"', ), + Bytes('usercertificate?', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded server certificate'), + ), ) def normalize_and_validate_email(self, email, config=None): diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index 54d47bb01450ec462577e552315e3d680b7648c3..e0004a5b01bf48887ca1bc429d8b37342e1c9cc0 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -267,10 +267,10 @@ class user(baseuser): 'mepmanagedentry', 'mobile', 'objectclass', 'ou', 'pager', 'postalcode', 'roomnumber', 'secretary', 'seealso', 'sn', 'st', 'street', 'telephonenumber', 'title', 'userclass', - 'preferredlanguage', + 'preferredlanguage', 'usercertificate', }, 'replaces': [ - '(targetattr = "givenname || sn || cn || displayname || title || initials || loginshell || gecos || homephone || mobile || pager || facsimiletelephonenumber || telephonenumber || street || roomnumber || l || st || postalcode || manager || secretary || description || carlicense || labeleduri || inetuserhttpurl || seealso || employeetype || businesscategory || ou || mepmanagedentry || objectclass")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Users";allow (write) groupdn = "ldap:///cn=Modify Users,cn=permissions,cn=pbac,$SUFFIX";)', + '(targetattr = "givenname || sn || cn || displayname || title || initials || loginshell || gecos || homephone || mobile || pager || facsimiletelephonenumber || telephonenumber || street || roomnumber || l || st || postalcode || manager || secretary || description || carlicense || labeleduri || inetuserhttpurl || seealso || employeetype || businesscategory || usercertificate || ou || mepmanagedentry || objectclass")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Users";allow (write) groupdn = "ldap:///cn=Modify Users,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': { 'User Administrators', -- 2.1.0
>From a877bad933cfb7454e670a684ce976194946f4c6 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 14 May 2015 01:45:16 -0400 Subject: [PATCH 11/11] Update cert-request to support user certs and profiles Part of: https://fedorahosted.org/freeipa/ticket/57 Part of: https://fedorahosted.org/freeipa/ticket/4938 --- ipalib/pkcs10.py | 1 + ipalib/plugins/cert.py | 174 ++++++++++++++++++++++++++----------------------- 2 files changed, 94 insertions(+), 81 deletions(-) diff --git a/ipalib/pkcs10.py b/ipalib/pkcs10.py index f35e200a2c1b47e2a2c8cffcf9b723f398fe3221..6299dfea43b7a3f4104f0b0ec78c4f105d9daf62 100644 --- a/ipalib/pkcs10.py +++ b/ipalib/pkcs10.py @@ -30,6 +30,7 @@ PEM = 0 DER = 1 SAN_DNSNAME = 'DNS name' +SAN_RFC822NAME = 'RFC822 Name' SAN_OTHERNAME_UPN = 'Other Name (OID.1.3.6.1.4.1.311.20.2.3)' SAN_OTHERNAME_KRB5PRINCIPALNAME = 'Other Name (OID.1.3.6.1.5.2.2)' diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index e4cb6dc0aa8b89368806b08674aae277b3653e8f..8fe5b9daf88870647067db37e6e3929cbc11626e 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -31,7 +31,8 @@ from ipalib import ngettext from ipalib.plugable import Registry from ipalib.plugins.virtual import * from ipalib.plugins.baseldap import pkey_to_value -from ipalib.plugins.service import split_principal +from ipalib.plugins.service import split_any_principal +from ipalib.plugins.certprofile import validate_profile_id import base64 import traceback from ipalib.text import _ @@ -122,6 +123,8 @@ http://www.ietf.org/rfc/rfc5280.txt """) +USER, HOST, SERVICE = range(3) + register = Registry() def validate_pkidate(ugettext, value): @@ -232,7 +235,7 @@ class cert_request(VirtualCommand): takes_options = ( Str('principal', label=_('Principal'), - doc=_('Service principal for this certificate (e.g. HTTP/test.example.com)'), + doc=_('Principal for this certificate (e.g. HTTP/test.example.com)'), ), Str('request_type', default=u'pkcs10', @@ -243,6 +246,10 @@ class cert_request(VirtualCommand): default=False, autofill=True ), + Str('profile_id', validate_profile_id, + label=_("Profile ID"), + doc=_("Certificate Profile to use"), + ) ) has_output_params = ( @@ -294,10 +301,9 @@ class cert_request(VirtualCommand): ca_enabled_check() ldap = self.api.Backend.ldap2 - principal = kw.get('principal') add = kw.get('add') request_type = kw.get('request_type') - service = None + profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) """ Access control is partially handled by the ACI titled @@ -310,9 +316,20 @@ class cert_request(VirtualCommand): taskgroup (directly or indirectly via role membership). """ - bind_principal = getattr(context, 'principal') - # Can this user request certs? - if not bind_principal.startswith('host/'): + principal = split_any_principal(kw.get('principal')) + servicename, principal_name, realm = principal + if servicename is None: + principal_type = USER + elif servicename == 'host': + principal_type = HOST + else: + principal_type = SERVICE + + bind_principal = split_any_principal(getattr(context, 'principal')) + bind_service, _, _ = bind_principal + + if bind_principal != principal: + # Can the bound principal request certs for another principal? self.check_access() try: @@ -323,57 +340,54 @@ class cert_request(VirtualCommand): raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) - if not bind_principal.startswith('host/'): + # host principals may bypass allowed ext check + if bind_service != 'host': for ext in extensions: operation = self._allowed_extensions.get(ext) if operation: self.check_access(operation) - # Ensure that the hostname in the CSR matches the principal - subject_host = subject.common_name #pylint: disable=E1101 - if not subject_host: + + # Ensure that the DN in the CSR matches the principal + cn = subject.common_name #pylint: disable=E1101 + if not cn: raise errors.ValidationError(name='csr', - error=_("No hostname was found in subject of request.")) + error=_("No Common Name was found in subject of request.")) - (servicename, hostname, realm) = split_principal(principal) - if subject_host.lower() != hostname.lower(): - raise errors.ACIError( - info=_("hostname in subject of request '%(subject_host)s' " - "does not match principal hostname '%(hostname)s'") % dict( - subject_host=subject_host, hostname=hostname)) + if principal_type in (SERVICE, HOST): + if cn.lower() != principal_name.lower(): + raise errors.ACIError( + info=_("hostname in subject of request '%(cn)s' " + "does not match principal hostname '%(hostname)s'") + % dict(cn=cn, hostname=principal_name)) + elif principal_type == USER: + pass # TODO require cn / emailAddress to match user for ext in extensions: if ext not in self._allowed_extensions: raise errors.ValidationError( name='csr', error=_("extension %s is forbidden") % ext) - for name_type, name in subjectaltname: - if name_type not in (pkcs10.SAN_DNSNAME, - pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME, - pkcs10.SAN_OTHERNAME_UPN): - raise errors.ValidationError( - name='csr', - error=_("subject alt name type %s is forbidden") % - name_type) - dn = None - service = None + principal_obj = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: - if servicename != 'host': - service = api.Command['service_show'](principal, all=True) - else: - service = api.Command['host_show'](hostname, all=True) - except errors.NotFound, e: - if not add: - raise errors.NotFound(reason=_("The service principal for " - "this request doesn't exist.")) - service = api.Command['service_add'](principal, force=True) - service = service['result'] - dn = service['dn'] + if principal_type == SERVICE: + principal_obj = api.Command['service_show'](principal, all=True) + elif principal_type == HOST: + principal_obj = api.Command['host_show'](principal_name, all=True) + elif principal_type == USER: + principal_obj = api.Command['user_show'](principal_name, all=True) + except errors.NotFound as e: + if principal_type != SERVICE or not add: + raise errors.NotFound( + reason=_("The principal for this request doesn't exist.")) + principal_obj = api.Command['service_add'](principal, force=True) + principal_obj = principal_obj['result'] + dn = principal_obj['dn'] - # We got this far so the service entry exists, can we write it? + # We got this far so the principal entry exists, can we write it? if not ldap.can_write(dn, "usercertificate"): raise errors.ACIError(info=_("Insufficient 'write' privilege " "to the 'userCertificate' attribute of entry '%s'.") % dn) @@ -382,13 +396,20 @@ class cert_request(VirtualCommand): for name_type, name in subjectaltname: if name_type == pkcs10.SAN_DNSNAME: name = unicode(name) + alt_principal_obj = None try: - if servicename == 'host': - altservice = api.Command['host_show'](name, all=True) - else: + if principal_type == HOST: + alt_principal_obj = api.Command['host_show'](name, all=True) + elif principal_type == SERVICE: altprincipal = '%s/%s@%s' % (servicename, name, realm) - altservice = api.Command['service_show']( + alt_principal_obj = api.Command['service_show']( altprincipal, all=True) + elif principal_type == USER: + raise errors.ValidationError( + name='csr', + error=_("subject alt name type %s is forbidden " + "for user principals") % name_type + ) except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this @@ -396,47 +417,35 @@ class cert_request(VirtualCommand): raise errors.NotFound(reason=_('The service principal for ' 'subject alt name %s in certificate request does not ' 'exist') % name) - altdn = altservice['result']['dn'] - if not ldap.can_write(altdn, "usercertificate"): - raise errors.ACIError(info=_( - "Insufficient privilege to create a certificate with " - "subject alt name '%s'.") % name) + if alt_principal_obj is not None: + altdn = alt_principal_obj['result']['dn'] + if not ldap.can_write(altdn, "usercertificate"): + raise errors.ACIError(info=_( + "Insufficient privilege to create a certificate " + "with subject alt name '%s'.") % name) elif name_type in (pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME, pkcs10.SAN_OTHERNAME_UPN): if name != principal: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " - "match requested service principal") % name) + "match requested principal") % name) + elif name_type == pkcs10.SAN_RFC822NAME: + if principal_type == USER: + pass # TODO require match to user email address + else: + raise errors.ValidationError( + name='csr', + error=_("subject alt name type %s is forbidden " + "for non-user principals") % name_type + ) else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % name_type) - if 'usercertificate' in service: - serial = x509.get_serial_number(service['usercertificate'][0], datatype=x509.DER) - # revoke the certificate and remove it from the service - # entry before proceeding. First we retrieve the certificate to - # see if it is already revoked, if not then we revoke it. - try: - result = api.Command['cert_show'](unicode(serial))['result'] - if 'revocation_reason' not in result: - try: - api.Command['cert_revoke'](unicode(serial), revocation_reason=4) - except errors.NotImplementedError: - # some CA's might not implement revoke - pass - except errors.NotImplementedError: - # some CA's might not implement get - pass - if not principal.startswith('host/'): - api.Command['service_mod'](principal, usercertificate=None) - else: - hostname = get_host_from_principal(principal) - api.Command['host_mod'](hostname, usercertificate=None) - # Request the certificate result = self.Backend.ra.request_certificate( - csr, 'caIPAserviceCert', request_type=request_type) + csr, profile_id, request_type=request_type) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) @@ -444,15 +453,18 @@ class cert_request(VirtualCommand): result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) - # Success? Then add it to the service entry. - if 'certificate' in result: - if not principal.startswith('host/'): - skw = {"usercertificate": str(result.get('certificate'))} + # Success? Then add it to the principal's entry + # (unless the profile tells us not to) + profile = api.Command['certprofile_show'](profile_id) + profile_store_issued = profile['result']['ipacertprofilestoreissued'] + if profile_store_issued and 'certificate' in result: + skw = {"usercertificate": str(result.get('certificate'))} + if principal_type == SERVICE: api.Command['service_mod'](principal, **skw) - else: - hostname = get_host_from_principal(principal) - skw = {"usercertificate": str(result.get('certificate'))} - api.Command['host_mod'](hostname, **skw) + elif principal_type == HOST: + api.Command['host_mod'](principal_name, **skw) + elif principal_type == USER: + api.Command['user_mod'](principal_name, **skw) return dict( result=result -- 2.1.0
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code