[Freeipa-devel] [PATCHES 0001-0007] Profile management

Fraser Tweedale ftweedal at redhat.com
Fri May 15 08:24:20 UTC 2015


Please find attached latest patches including new patches:

- 0006 enable LDAP-based profiles in Dogtag on upgrade
- 0007 import included profiles during install or upgrade

There is one TODO in the patches where some more code is needed on
Dogtag side, and another TODO (not in patches) to migrate
caIPAserviceCert profile to DefaultService profile and switch to
using DefaultService for cerificate issuance (as the default
profile).

Jan and Martin, further comments to earlier reviews inline.

Cheers,
Fraser

On Wed, May 13, 2015 at 10:39:55AM +0200, Jan Cholasta wrote:
> Dne 13.5.2015 v 10:36 Martin Basti napsal(a):
> >On 13/05/15 10:06, Jan Cholasta wrote:
> >>Hi,
> >>
> >>Dne 5.5.2015 v 10:38 Martin Basti napsal(a):
> >>>On 05/05/15 08:29, Fraser Tweedale wrote:
> >>>>On Mon, May 04, 2015 at 06:35:45PM +0200, Martin Basti wrote:
> >>>>>On 04/05/15 15:36, Fraser Tweedale wrote:
> >>>>>>Hello,
> >>>>>>
> >>>>>>Please review the first cut of the 'certprofile' command and other
> >>>>>>changes associated with the Certificate Profiles feature[1].
> >>>>>>
> >>>>>>Custom profiles can't be used yet because 'cert-request' has not
> >>>>>>been updated, but you can manage the profiles (find, show, import,
> >>>>>>modify, delete).  There's a bit more work to do on profile
> >>>>>>management and a lot more to do for using profiles and sub-CAs.  I
> >>>>>>am tracking my progress on etherpad[2] so if you are reviewing check
> >>>>>>there for the TODO list and some commentary.
> >>>>>>
> >>>>>>If you want to test: for f21, please use Dogtag from my copr[2].
> >>>>>>For f22 the required version is in updates-testing (or my copr).
> >>>>>>
> >>>>>>In summary: this is not the whole feature, just the first functional
> >>>>>>part.  Since it is my first experience developing in the IPA
> >>>>>>framework I want to get patches out so you can point out all the
> >>>>>>things I did wrong or overlooked, and I can fix them. Don't hold
> >>>>>>back :)
> >>>>>>
> >>>>>>[1] http://www.freeipa.org/page/V4/Certificate_Profiles
> >>>>>>[2] http://idm.etherpad.corp.redhat.com/rhel72-cert-mgmt-progress
> >>>>>>[3] http://copr.fedoraproject.org/coprs/ftweedal/freeipa/
> >>>>>>
> >>>>>>
> >>>>>Thank you for patches, I have no idea what kind of dogtag magic is
> >>>>>happening
> >>>>>there, but I have a few comments related to IPA:
> >>>>>
> >>>>Thanks for reviewing, Martin.  Comments inline.
> >>>You are welcome, comments inline.
> >>>Martin^2
> >>>>
> >>>>>Upgrade:
> >>>>>
> >>>>>1)
> >>>>>
> >>>>>+        config.set("CA", "pki_profiles_in_ldap", "True")
> >>>>>
> >>>>>IMO this will work only for new installations. For upgrade you may
> >>>>>need to
> >>>>>add this to ipa-upgradeconfig
> >>>>>
> >>>>OK.
> >>>>
> >>>>>2)
> >>>>>+dn: cn=certprofiles,cn=etc,$SUFFIX
> >>>>>+changetype: add
> >>>>>+objectClass: nsContainer
> >>>>>+objectClass: top
> >>>>>+cn: certprofiles
> >>>>>
> >>>>>IMO this will work only for new installations. For upgrade you may
> >>>>>need to
> >>>>>add it into update file as well, with the 'default' keyword
> >>>>>
> >>>>I don't understand about the 'default' keyword - can you expain this
> >>>>some more?
> >>>In an upgrade file:
> >>>
> >>>dn: cn=certprofiles,cn=etc,$SUFFIX
> >>>default:objectClass: nsContainer
> >>>default:objectClass: top
> >>>default:cn: certprofiles
> >>
> >>Maybe we should do what DNS does and have a container for CA specific
> >>stuff in the suffix: cn=ca,$SUFFIX.
> >>
> >>The container would be created only if CA is installed.
> >>
> >>Certificate profile container would then be
> >>cn=certprofiles,cn=ca,$SUFFIX.
> >>
I haven't changed this for the current patchset.  What are the
implications / motivations for changing it.

> >>>>>3)
> >>>>>Your patch 0004 will work on new installations only. You may need
> >>>>>to add
> >>>>>that new step into ipa-upgradeconfig.
> >>>>>
> >>>>>Must be that step there during installation?
> >>>>>If not you can create just one update file, which will be applied at
> >>>>>the end
> >>>>>of installation and during upgrade.
> >>>>>
> >>>>This change must be made to the Dogtag directory (not IPA) - can an
> >>>>update file be used to do that?  If not, is ipa-upgradeconfig the
> >>>>best place to make this change?
> >>>If it is change in LDAP, you can use updatefile:
> >>>
> >>>dn: cn=aclResources,$SUFFIX
> >>>add:resourceACLS: certServer.profile.configuration:read,modify:allow
> >>>(read,modify) group="Certificate Manager Agents":Certificate Manager
> >>>agents may modify (create/update/delete) and read profiles
> >>>
> >>>Please temporarily use my patch freeipa-mbasti-231-4, (which will be
> >>>pushed soon) to avoid issues with CSV
> >>
> >>Note that this update should be done only if CA is installed.
> >In that case, you must create update plugins.
> 
> I would prefer a CAInstance method called during install and in
> ipa-upgradeconfig. So more or less what Fraser already did, except the
> ipa-upgradeconfig part.
> 
Patch 0004 was updated and now has CAInstance method during install,
and ipa-upgradeconfig method for upgrade.

> >
> >Martin^2
> >>
> >>>>>Other issues:
> >>>>>1)
> >>>>>I do not see modifications in API.txt file
> >>>>>
> >>>>>2)
> >>>>>We use new shorter license header
> >>>>>#
> >>>>># Copyright (C) 2015  FreeIPA Contributors see COPYING for license
> >>>>>#
> >>>>>
> >>>>>3)
> >>>>>+from ipalib.plugins.baseldap import \
> >>>>>+    LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete, LDAPUpdate,
> >>>>>LDAPRetrieve
> >>>>>
> >>>>>please use 'import ( modules, .. )' instead of '\'
> >>>>>
> >>>>>4)
> >>>>>+    if method == 'POST' \
> >>>>>+            and 'content-type' not in (str(k).lower() for k in
> >>>>>headers.viewkeys()):
> >>>>>
> >>>>>again, please use if ( ... ): instead \
> >>>>>
> >>>>>5)
> >>>>>+import  ipalib.errors as errors
> >>>>>in dogtag.py
> >>>>>
> >>>>>Can you use from ipalib import errors, instead?
> >>>>>
> >>>>>6)
> >>>>>+    def __exit__(self, exc_type, exc_value, traceback):
> >>>>>+        """Log out of the REST API"""
> >>>>>+        # TODO logout
> >>>>>+        self.cookie = None
> >>>>>
> >>>>>This is forgotten, or will be this fixed later?
> >>>>>
> >>>>>7)
> >>>>>+            if not explanation: print resp_body  # NOCOMMIT
> >>>>>
> >>>>These are all fixed for the next patchset.
> >>>>
> >>>>Thanks!
> >>>>Fraser
> >>>>
> >>
> >>8) You can do:
> >>
> >>        Str('cn',
> >>            primary_key=True,
> >>            cli_name='id',
> >>            label=_('Profile ID'),
> >>            doc=_('Profile ID for referring to this profile'),
> >>            pattern='^[a-zA-Z]\w*$',
> >>            pattern_errmsg=_('invalid Profile ID'),
> >>        ),
> >>
> >>instead of:
> >>
> >>    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
> >>
> >>    ...
> >>
> >>        Str('cn', validate_profile_id,
> >>            primary_key=True,
> >>            cli_name='id',
> >>            label=_('Profile ID'),
> >>            doc=_('Profile ID for referring to this profile'),
> >>        ),
> >>
This is nice, but I have kept the separate method so that the
cert-request command can use the same routine for validating the
profile id (this will be in a subsequent patch).

> >>9) Please don't invent new attributes (ipaCertProfileSummary) when you
> >>can use existing ones (description).
> >>
> >>
> >>10) All the commands should call ipalib.plugins.cert.ca_enabled_check().
> >>
> >>
> >>11) I think the File parameter of certprofile_import should be called
> >>just 'file'.
> >>
9, 10, 11 were addressed for this patchset.

> >>
> >>12) IMO the profile backend should be merged in to the ra backend. I
> >>don't see a need to have these two separate.
> >>
> >>
> >>Honza
> >>
> >
> >
> 
> 
> -- 
> Jan Cholasta
-------------- next part --------------
>From 1b54e1572f4744aa0ce1b0bcb5c658a21e4b2fa7 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <frase at frase.id.au>
Date: Mon, 20 Apr 2015 23:20:19 -0400
Subject: [PATCH 1/7] 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 8ccfd1a822fab557dc1b6bf6d0e7de3ef495efbb..7241494749480375b548c7fb8930b3f93d728e79 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -505,6 +505,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

-------------- next part --------------
>From c9dac576a9a73878286d09967bb54bbc0e3555a5 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <frase at frase.id.au>
Date: Tue, 21 Apr 2015 02:24:10 -0400
Subject: [PATCH 2/7] 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     | 6 ++++++
 ipaserver/install/dsinstance.py           | 1 +
 4 files changed, 11 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..1d74842ed544678dd080e34e1eef88983852f1d2 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -429,3 +429,9 @@ cn: ${REALM}_id_range
 ipaBaseID: $IDSTART
 ipaIDRangeSize: $IDRANGE_SIZE
 ipaRangeType: ipa-local
+
+dn: cn=certprofiles,cn=etc,$SUFFIX
+changetype: add
+objectClass: nsContainer
+objectClass: top
+cn: certprofiles
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 8a76e773f0a464529331d9e2e459c9cc5ea0522e..b562792f72e6821925911902fd49e0829e461559 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -55,6 +55,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

-------------- next part --------------
>From 1b3f4a3a030f2d71f3e2c3d69d70f7258c4f6a52 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Wed, 29 Apr 2015 06:07:58 -0400
Subject: [PATCH 3/7] 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

-------------- next part --------------
>From fb2b7ef098b34dff4abec8f50b44c8f9cebd8812 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Thu, 30 Apr 2015 23:50:41 -0400
Subject: [PATCH 4/7] Add ACL to allow CA agent to modify profiles

Part of: https://fedorahosted.org/freeipa/ticket/57
---
 install/tools/ipa-upgradeconfig | 33 +++++++++++++++++++++++++++++++++
 ipaserver/install/cainstance.py | 14 ++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
index 8159ce244b873962df36409b884bc3ee70621f2f..d439e5336e16a2e79c8cf2e957f019a1ffc0ce4b 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,36 @@ 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
+
+    upgrade_state_args = ('dogtag', 'agent_allow_profile_modify')
+    if sysupgrade.get_upgrade_state(*upgrade_state_args):
+        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
+
+    dn = DN(('cn', 'aclResources'), ('o', 'ipaca'))
+    modlist = [(ldap.MOD_ADD, 'resourceACLS', [
+        'certServer.profile.configuration:read,modify:allow (read,modify) '
+        'group="Certificate Manager Agents":'
+        'Certificate Manager agents may modify (create/update/delete) and read profiles'
+    ])]
+    api.Backend.ldap2.conn.modify_s(str(dn), modlist)
+
+    sysupgrade.set_upgrade_state(*upgrade_state_args + (True,))
+    return True
+
+
 def upgrade_ipa_profile(ca, domain, fqdn):
     """
     Update the IPA Profile provided by dogtag
@@ -1420,6 +1452,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 7241494749480375b548c7fb8930b3f93d728e79..81fd8a47d3cebe0659d5b3158928f578c0a75059 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -471,6 +471,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:
@@ -942,6 +943,19 @@ class CAInstance(DogtagInstance):
 
         conn.unbind()
 
+    def __configure_profiles_acl(self):
+        """Allow the Certificate Manager Agents group to modify profiles."""
+        dn = DN(('cn', 'aclResources'), self.basedn)
+        modlist = [(ldap.MOD_ADD, 'resourceACLS', [
+            'certServer.profile.configuration:read,modify:allow (read,modify) '
+            'group="Certificate Manager Agents":'
+            'Certificate Manager agents may modify (create/update/delete) and read profiles'
+        ])]
+        conn = ipaldap.IPAdmin(self.fqdn, self.ds_port)
+        conn.do_simple_bind(DN(('cn', 'Directory Manager')), self.dm_password)
+        conn.modify_s(dn, modlist)
+        conn.unbind()
+
     def __run_certutil(self, args, database=None, pwd_file=None, stdin=None):
         if not database:
             database = self.ra_agent_db
-- 
2.1.0

-------------- next part --------------
>From bc8fb17fa5e76e0d15d740764e26752f7eab644f Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Thu, 30 Apr 2015 04:55:29 -0400
Subject: [PATCH 5/7] 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 |   4 +
 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, 529 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 933b57cb93e833981867953b516a67484f13dca3..9fa283afff80135280a884923d70404924cf8fa9 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=etc,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=etc,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=etc,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=etc,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 f747765d7f9c87761fed0277cd59d1bc3fbd57e9..1efab43b48ca304a197d7ba5f0b01d44e7f500ff 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..e6c25cdf3d90aae1d4c3ff031a1ed94f975a8a99
--- /dev/null
+++ b/install/updates/40-certprofile.update
@@ -0,0 +1,4 @@
+dn: cn=certprofiles,cn=etc,$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 32af498190a23ddfd202a5cad75409f60a70d78b..fd474bcd12c935e1485959c5763d6773d31c8bd7 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..02ba78e4b024c32b602c74e123ce305fbf247b8f 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', 'etc'))),
 
     # 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..a9918451baa2a264ada41ba4efc39f2bdbd11501
--- /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
+
+
+ at 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=etc,$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=etc,$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=etc,$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'},
+        },
+    }
+
+
+
+ at 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)
+
+
+ at 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)
+
+
+ at 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
+
+
+ at 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
+
+
+ at 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 <jderose at redhat.com>
 #   Rob Crittenden <rcritten@@redhat.com>
 #   John Dennis <jdennis at redhat.com>
+#   Fraser Tweedale <ftweedal at 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

-------------- next part --------------
>From 7b398804189db9d991443ba7d24db6aa04a8cf52 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Mon, 11 May 2015 23:38:41 -0400
Subject: [PATCH 6/7] Enable LDAP-based profiles in CA on upgrade

Part of: https://fedorahosted.org/freeipa/ticket/4560
---
 install/tools/ipa-upgradeconfig | 44 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 40 insertions(+), 4 deletions(-)

diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
index d439e5336e16a2e79c8cf2e957f019a1ffc0ce4b..c6c602dc6b4582c24e4ca751ab2f91f8e683dffa 100755
--- a/install/tools/ipa-upgradeconfig
+++ b/install/tools/ipa-upgradeconfig
@@ -327,10 +327,6 @@ def setup_firefox_extension(fstore):
 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
-
     upgrade_state_args = ('dogtag', 'agent_allow_profile_modify')
     if sysupgrade.get_upgrade_state(*upgrade_state_args):
         return False
@@ -354,6 +350,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
@@ -1453,6 +1488,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

-------------- next part --------------
>From 57af38f582493ba72df40c668000580c7db6c48a Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Mon, 11 May 2015 21:17:48 -0400
Subject: [PATCH 7/7] 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          |  16 ++-
 install/tools/ipa-upgradeconfig           |  49 ++++---
 ipapython/dogtag.py                       |   5 +
 ipaserver/install/cainstance.py           | 216 ++++--------------------------
 ipaserver/plugins/dogtag.py               |  14 +-
 10 files changed, 207 insertions(+), 220 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 608242b5adbc43efbbf0ae30a6d7a933bebc1084..2ebd0072389c51d23717edc678d69b4ef895d874 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -709,6 +709,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 56a43770d95387762bce09634bd1056ba7f20576..cb2a2e531e45e5faeadaf808107ffe22e336e670 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -1151,6 +1151,9 @@ def main():
     os.chmod(target_fname, 0644)
 
     api.bootstrap(**cfg)
+    if setup_ca:
+        # ensure profile backend is available
+        import ipaserver.plugins.dogtag
     api.finalize()
 
     if not options.unattended:
@@ -1164,6 +1167,9 @@ def main():
                       % (host_name, system_hostname))
         # configure /etc/sysconfig/network to contain the custom hostname
         tasks.backup_and_replace_hostname(fstore, sstore, host_name)
+        # update `api.env.ca_host` to correct hostname
+        # https://fedorahosted.org/freeipa/ticket/4936
+        api.env.ca_host = host_name
 
     # Create DS user/group if it doesn't exist yet
     dsinstance.create_ds_user()
@@ -1312,6 +1318,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()
+
     # Create a BIND instance
     bind = bindinstance.BindInstance(fstore, dm_password)
     bind.setup(host_name, ip_addresses, realm_name, domain_name, dns_forwarders,
@@ -1319,7 +1331,9 @@ def main():
                ca_configured=setup_ca,
                no_dnssec_validation=options.no_dnssec_validation)
     if options.setup_dns:
-        api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password)
+        if not api.Backend.ldap2.isconnected():
+            api.Backend.ldap2.connect(
+                bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password)
 
         bind.create_instance()
         dnskeysyncd = dnskeysyncinstance.DNSKeySyncInstance(fstore, dm_password)
diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
index c6c602dc6b4582c24e4ca751ab2f91f8e683dffa..9e794060e4a520499c3c4c3c548eec7a5b551379 100755
--- a/install/tools/ipa-upgradeconfig
+++ b/install/tools/ipa-upgradeconfig
@@ -389,32 +389,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():
@@ -1337,6 +1339,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()
@@ -1484,7 +1487,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),
@@ -1498,6 +1501,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 81fd8a47d3cebe0659d5b3158928f578c0a75059..94c01de75ef8c3a4193c39cf8bcbe02dddda860e 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)
             self.step("configuring certificate server to start on boot",
                       self.enable)
@@ -1136,94 +1132,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):
         """
@@ -1278,13 +1186,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):
         enabled = self.restore_state("enabled")
 
@@ -1421,100 +1322,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):
         """
@@ -1600,6 +1407,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



More information about the Freeipa-devel mailing list