From 2ee07f3447de32940cf618af53fdc8922e9806c0 Mon Sep 17 00:00:00 2001
From: Ade Lee <alee@redhat.com>
Date: Tue, 18 Mar 2014 11:23:30 -0400
Subject: [PATCH] Add a DRM to IPA

This patch adds the capability of installing a Dogtag DRM
to an IPA instance.  With this patch,  a DRM is NOT configured
by default when ipa-server-install is run.  Rather, the command
ipa-drm-install must be executed on an instance on which a Dogtag
CA has already been configured.

The DRM shares the same tomcat instance and DS instance as the
Dogtag CA.  Moreover, the same admin user/agent (and agent cert) can
be used for both subsystems.  Certmonger is also confgured to
monitor the new subsystem certificates.

To create a clone DRM, simply execute ipa-drm-install <replica_file>
on a replica on which a Dogtag CA has already been replicated.
ipa-drm-install will use the security domain to detect whether the
system being installed is a replica, and wil prompt for a replica
file if needed.

The install scripts have been refactored somewhat to minimize
duplication of code.  A new base class dogtagintance.py has
been introduced containing code that is common to DRM and CA
installs.  This will become very useful when we add more PKI
subsystems.

The DRM will install its database as a subtree of o=ipaca,
specifically o=ipadrm,o=ipaca.  This means that replication
agreements created to replicate CA data will also replicate DRM
data.  No new replication agreements are required.

Added dogtag plugin for DRM.  This is an initial commit providing
the basic vault functionality needed for vault.  This plugin will
likely be modified as we create the code to call some of these
functions.
---
 freeipa.spec.in                          |   2 +
 install/conf/ipa-pki-proxy.conf          |   6 +-
 install/po/Makefile.in                   |   1 +
 install/restart_scripts/renew_ca_cert    |  18 +-
 install/tools/Makefile.am                |   1 +
 install/tools/ipa-ca-install             |  60 ++--
 install/tools/ipa-dns-install            |   1 +
 install/tools/ipa-drm-install            | 232 ++++++++++++++
 install/tools/ipa-replica-install        |  80 +++--
 install/tools/ipa-server-install         |  41 ++-
 install/tools/ipa-upgradeconfig          |  67 +---
 ipapython/dogtag.py                      |  13 +-
 ipaserver/install/cainstance.py          | 504 ++++++++++++-------------------
 ipaserver/install/dogtaginstance.py      | 392 ++++++++++++++++++++++++
 ipaserver/install/drminstance.py         | 354 ++++++++++++++++++++++
 ipaserver/install/dsinstance.py          |  83 +++++
 ipaserver/install/installutils.py        |  94 ++++--
 ipaserver/install/ipa_replica_prepare.py |   1 +
 ipaserver/plugins/dogtag.py              | 330 +++++++++++++++-----
 19 files changed, 1690 insertions(+), 590 deletions(-)
 create mode 100644 install/tools/ipa-drm-install
 create mode 100644 ipaserver/install/dogtaginstance.py
 create mode 100644 ipaserver/install/drminstance.py

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 6b1f0a95299f674f8a2062f5b3897e4decaba223..569aeba7d1234af3bad61fa7f2ef6bffa59b84a7 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -112,6 +112,7 @@ Requires: selinux-policy >= 3.12.1-176
 Requires(post): selinux-policy-base
 Requires: slapi-nis >= 0.47.7
 Requires: pki-ca >= 10.1.1
+Requires: pki-kra >= 10.1.1
 Requires: dogtag-pki-server-theme
 %if 0%{?rhel}
 Requires: subscription-manager
@@ -571,6 +572,7 @@ fi
 %{_sbindir}/ipa-restore
 %{_sbindir}/ipa-ca-install
 %{_sbindir}/ipa-dns-install
+%{_sbindir}/ipa-drm-install
 %{_sbindir}/ipa-server-install
 %{_sbindir}/ipa-replica-conncheck
 %{_sbindir}/ipa-replica-install
diff --git a/install/conf/ipa-pki-proxy.conf b/install/conf/ipa-pki-proxy.conf
index 224cdd45b5b5f72671a179570fd15772fe8cfaab..4368896b93c38bfce0f4b6cff9b0b201a3605dbf 100644
--- a/install/conf/ipa-pki-proxy.conf
+++ b/install/conf/ipa-pki-proxy.conf
@@ -1,4 +1,4 @@
-# VERSION 4 - DO NOT REMOVE THIS LINE
+# VERSION 5 - 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">
+<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">
     NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
     NSSVerifyClient none
     ProxyPassMatch ajp://localhost:$DOGTAG_PORT
@@ -19,7 +19,7 @@ ProxyRequests Off
 </LocationMatch>
 
 # matches for agent port and eeca port
-<LocationMatch "^/ca/agent/ca/displayBySerial|^/ca/agent/ca/doRevoke|^/ca/agent/ca/doUnrevoke|^/ca/agent/ca/updateDomainXML|^/ca/eeca/ca/profileSubmitSSLClient">
+<LocationMatch "^/ca/agent/ca/displayBySerial|^/ca/agent/ca/doRevoke|^/ca/agent/ca/doUnrevoke|^/ca/agent/ca/updateDomainXML|^/ca/eeca/ca/profileSubmitSSLClient|^/kra/agent/kra/connector">
     NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
     NSSVerifyClient require
     ProxyPassMatch ajp://localhost:$DOGTAG_PORT
diff --git a/install/po/Makefile.in b/install/po/Makefile.in
index 6dca615c13acf8d40030da0318a1103f4ece1181..c8d7b6353e2b2b00f6e9e6f8cfe3bcc8f84ae73f 100644
--- a/install/po/Makefile.in
+++ b/install/po/Makefile.in
@@ -47,6 +47,7 @@ PY_EXPLICIT_FILES = \
      install/tools/ipa-csreplica-manage \
      install/tools/ipactl \
      install/tools/ipa-dns-install \
+     install/tools/ipa-drm-install \
      install/tools/ipa-ldap-updater \
      install/tools/ipa-managed-entries \
      install/tools/ipa-nis-manage \
diff --git a/install/restart_scripts/renew_ca_cert b/install/restart_scripts/renew_ca_cert
index a4959bed9b5efdf9d8179441e21b967459e1cdc5..68ea217fbf6cc9ef3a6ca9d7f886b588f5bf56df 100644
--- a/install/restart_scripts/renew_ca_cert
+++ b/install/restart_scripts/renew_ca_cert
@@ -21,17 +21,14 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sys
-import os
 import syslog
-import tempfile
-import shutil
 import traceback
 
-from ipapython import dogtag, certmonger, ipautil
-from ipalib import api, errors, x509, util
-from ipaserver.install import certs, cainstance, installutils
-from ipaserver.plugins.ldap2 import ldap2
+from ipapython import dogtag, ipautil
 from ipaplatform import services
+from ipalib import api
+from ipaserver.install import certs, cainstance
+
 
 def main():
     nickname = sys.argv[1]
@@ -68,9 +65,8 @@ def main():
         syslog.syslog(syslog.LOG_ERR, 'No certificate %s found.' % nickname)
         sys.exit(1)
 
-    cainstance.update_cert_config(nickname, cert, configured_constants)
-
     ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
+    ca.update_cert_config(nickname, cert, configured_constants)
     if ca.is_renewal_master():
         cainstance.update_people_entry(cert)
 
@@ -93,7 +89,9 @@ def main():
     # off the servlet to verify that the CA is actually up and responding so
     # when this returns it should be good-to-go. The CA was stopped in the
     # pre-save state.
-    syslog.syslog(syslog.LOG_NOTICE, 'Starting %s' % dogtag_service.service_name)
+    syslog.syslog(
+        syslog.LOG_NOTICE,
+        'Starting %s' % dogtag_service.service_name)
     try:
         dogtag_service.start(dogtag_instance)
     except Exception, e:
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 485be91b7bca2b0f3822a70d0f027793208918c1..6740d32f031be91640486af330086281a394ccf9 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -7,6 +7,7 @@ SUBDIRS = 			\
 sbin_SCRIPTS =			\
 	ipa-ca-install		\
 	ipa-dns-install		\
+	ipa-drm-install		\
 	ipa-server-install	\
 	ipa-adtrust-install	\
 	ipa-replica-conncheck	\
diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index 3c9307edf65a921a3193775c605729ae422d16e8..c7d76a6cd48d38d6b02bc6a1503152d10b2499a5 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -19,21 +19,18 @@
 #
 
 import sys
-import socket
-
-import os, shutil
+import os
+import shutil
 
 from ipapython import ipautil
 
-from ipaserver.install import installutils, service
-from ipaserver.install import certs
-from ipaserver.install.installutils import (HostnameLocalhost, ReplicaConfig,
-        expand_replica_info, read_replica_info, get_host_name, BadHostError,
-        private_ccache, read_replica_info_dogtag_port)
+from ipaserver.install import installutils
+from ipaserver.install.installutils import (
+    private_ccache, create_replica_config)
 from ipaserver.install import dsinstance, cainstance, bindinstance
 from ipaserver.install.replication import replica_conn_check
 from ipapython import version
-from ipalib import api, util
+from ipalib import api
 from ipapython.dn import DN
 from ipapython.config import IPAOptionParser
 from ipapython import sysrestore
@@ -45,6 +42,7 @@ from ipaplatform.paths import paths
 log_file_name = paths.IPAREPLICA_CA_INSTALL_LOG
 REPLICA_INFO_TOP_DIR = None
 
+
 def parse_options():
     usage = "%prog [options] REPLICA_FILE"
     parser = IPAOptionParser(usage=usage, version=version.VERSION)
@@ -72,9 +70,11 @@ def parse_options():
 
     return safe_options, options, args[0]
 
+
 def get_dirman_password():
     return installutils.read_password("Directory Manager (existing master)", confirm=False, validate=False)
 
+
 def install_dns_records(config, options):
 
     if not bindinstance.dns_container_exists(config.master_host_name,
@@ -89,7 +89,8 @@ def install_dns_records(config, options):
         bind.add_ipa_ca_dns_records(config.host_name, config.domain_name)
     finally:
         if api.Backend.ldap2.isconnected():
-             api.Backend.ldap2.disconnect()
+            api.Backend.ldap2.disconnect()
+
 
 def main():
     safe_options, options, filename = parse_options()
@@ -130,38 +131,11 @@ def main():
             sys.exit("Directory Manager password required")
 
     if not options.admin_password and not options.skip_conncheck and \
-        options.unattended:
-            sys.exit('admin password required')
-
-    try:
-        top_dir, dir = expand_replica_info(filename, dirman_password)
-        global REPLICA_INFO_TOP_DIR
-        REPLICA_INFO_TOP_DIR = top_dir
-    except Exception, e:
-        print "ERROR: Failed to decrypt or open the replica file."
-        print "Verify you entered the correct Directory Manager password."
-        sys.exit(1)
+            options.unattended:
+        sys.exit('admin password required')
 
-    config = ReplicaConfig()
-    read_replica_info(dir, config)
-    config.dirman_password = dirman_password
-    try:
-        host = get_host_name(options.no_host_dns)
-    except BadHostError, e:
-        root_logger.error(str(e))
-        sys.exit(1)
-    if config.host_name != host:
-        try:
-            print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host)
-            if not ipautil.user_input("This may cause problems. Continue?", True):
-                sys.exit(0)
-            config.host_name = host
-            print ""
-        except KeyboardInterrupt:
-            sys.exit(0)
-    config.dir = dir
+    config = create_replica_config(dirman_password, filename, options)
     config.setup_ca = True
-    config.ca_ds_port = read_replica_info_dogtag_port(config.dir)
 
     if not ipautil.file_exists(config.dir + "/cacert.p12"):
         print 'CA cannot be installed in CA-less setup.'
@@ -185,7 +159,7 @@ def main():
                    ipautil.realm_to_suffix(config.realm_name))
 
     # This is done within stopped_service context, which restarts CA
-    CA.enable_client_auth_to_db()
+    CA.enable_client_auth_to_db(CA.dogtag_constants.CS_CFG_PATH)
 
     # Install CA DNS records
     install_dns_records(config, options)
@@ -213,8 +187,8 @@ if __name__ == '__main__':
     try:
         with private_ccache():
             installutils.run_script(main, log_file_name=log_file_name,
-                    operation_name='ipa-ca-install',
-                    fail_message=fail_message)
+                                    operation_name='ipa-ca-install',
+                                    fail_message=fail_message)
     finally:
         # always try to remove decrypted replica file
         try:
diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index 5e191974b6b91cd893b483ad56a9aa723770eafe..c9ea63ce3ce719324737c94fc35996a294cbb10f 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -32,6 +32,7 @@ from ipalib import api, errors, util
 from ipaplatform.paths import paths
 from ipapython.config import IPAOptionParser
 from ipapython.ipa_log_manager import standard_logging_setup, root_logger
+from ipapython.ipautil import DN
 
 log_file_name = paths.IPASERVER_INSTALL_LOG
 
diff --git a/install/tools/ipa-drm-install b/install/tools/ipa-drm-install
new file mode 100644
index 0000000000000000000000000000000000000000..a744e64d2aa0ba8eeb0ec4f6df4bdb74c2936f46
--- /dev/null
+++ b/install/tools/ipa-drm-install
@@ -0,0 +1,232 @@
+#! /usr/bin/python2 -E
+# Authors: Ade Lee <alee@redhat.com>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+import os
+import sys
+from ConfigParser import SafeConfigParser, NoOptionError
+
+from ipalib import api
+from ipaplatform import services
+from ipapython import version
+from ipapython import certmonger
+from ipapython import dogtag
+from ipapython import ipautil
+from ipapython.config import IPAOptionParser
+from ipapython.ipa_log_manager import *
+from ipaserver.install import dogtaginstance
+from ipaserver.install import drminstance
+from ipaserver.install import dsinstance
+from ipaserver.install import installutils
+from ipaserver.install import service
+from ipaserver.install.installutils import (
+    read_password, read_replica_info_drm_enabled,
+    create_replica_config)
+
+log_file_name = "/var/log/ipa-drm-install.log"
+
+
+def uninstall(realm_name):
+    dogtag_constants = dogtag.configured_constants()
+
+    drm_instance = drminstance.DRMInstance(
+        realm_name, dogtag_constants=dogtag_constants)
+    drm_instance.stop_tracking_certificates(dogtag_constants)
+    if drm_instance.is_installed():
+        drm_instance.uninstall()
+
+    dirs = [dogtag_constants.ALIAS_DIR]
+    ids = certmonger.check_state(dirs)
+    if ids:
+        root_logger.error(
+            "Some certificates may still be tracked by certmonger.\n"
+            "This will cause re-installation to fail.\n"
+            "Start the certmonger service and list the certificates "
+            "being tracked\n"
+            "# getcert list\nThese may be untracked by executing\n"
+            "# getcert stop-tracking -i <request_id>\n"
+            "for each id in: %s"
+            % ', '.join(ids))
+
+
+def parse_options():
+    usage = "%prog [options] [replica_file]"
+    parser = IPAOptionParser(usage=usage, version=version.VERSION)
+    parser.add_option("-d", "--debug", dest="debug", action="store_true",
+                      default=False, help="gather extra debugging information")
+    parser.add_option("-p", "--password", dest="password", sensitive=True,
+                      help="Directory Manager (existing master) password")
+    parser.add_option(
+        "--no-host-dns", dest="no_host_dns", action="store_true",
+        default=False,
+        help="Do not use DNS for hostname lookup during installation")
+    parser.add_option("-U", "--unattended", dest="unattended",
+                      action="store_true", default=False,
+                      help="unattended installation never prompts the user")
+    parser.add_option(
+        "", "--uninstall", dest="uninstall",
+        action="store_true", default=False,
+        help="uninstall an existing installation. The uninstall can "
+            "be run with --unattended option")
+
+    options, args = parser.parse_args()
+    safe_options = parser.get_safe_opts(options)
+
+    fname = args[0] if len(args) > 0 else None
+    return safe_options, options, fname
+
+
+def main():
+    log_file = "/var/log/ipa-drm-install.log"
+    safe_options, options, replica_file = parse_options()
+
+    if os.geteuid() != 0:
+        sys.exit("\nYou must be root to run this script.\n")
+
+    if options.uninstall:
+        log_file = "/var/log/ipa-drm-uninstall.log"
+
+    standard_logging_setup(log_file, debug=options.debug)
+
+    print "\nThe log file for this operation can be found in " + log_file_name
+    root_logger.debug('%s was invoked with options: %s' %
+                      (sys.argv[0], safe_options))
+
+    if options.unattended and options.password is None:
+        sys.exit("Directory Manager password must be specified using -p"
+                 " in unattended mode")
+
+    dm_password = options.password or \
+        read_password("Directory Manager", confirm=False)
+    if dm_password is None:
+        sys.exit("Directory Manager password required")
+
+    p = SafeConfigParser()
+    p.read("/etc/ipa/default.conf")
+
+    try:
+        host_name = p.get('global', 'host')
+        realm_name = p.get('global', 'realm')
+        domain_name = p.get('global', 'domain')
+    except NoOptionError as e:
+        print "\nA required parameter is missing from /etc/ipa/default.conf\n"
+        raise e
+
+    try:
+        dogtag_version = int(p.get('global', 'dogtag_version'))
+        ra_plugin = p.get('global', 'ra_plugin')
+        enable_ra = p.get('global', 'enable_ra')
+    except NoOptionError as e:
+        print "\nA Dogtag CA must first be installed, or a required " \
+              "parameter is missing from /etc/ipa/default.conf\n"
+        raise e
+
+    try:
+        enable_drm = p.get('global', 'enable_drm')
+    except NoOptionError:
+        enable_drm = None
+
+    subject = dsinstance.DsInstance().find_subject_base()
+
+    if options.uninstall:
+        if enable_drm is None:
+            sys.exit("There is no DRM installed on this system")
+        return uninstall(realm_name)
+
+    if enable_drm is not None and enable_drm == 'True':
+        sys.exit("DRM is already installed.")
+
+    ca_installed = enable_ra is not None and enable_ra == "True" and \
+        ra_plugin is not None and ra_plugin == "dogtag"
+
+    if ca_installed:
+        if dogtag_version is not None and dogtag_version >= 10:
+            # correct dogtag version of CA installed
+            pass
+        else:
+            sys.exit("Dogtag must be version 10.1 or above to install DRM")
+    else:
+        sys.exit("Dogtag CA is not installed.  Please install the CA first")
+
+    installing_replica = dogtaginstance.is_installing_replica("KRA")
+    if installing_replica:
+        if replica_file is None:
+            sys.exit("A replica file is required.")
+        if not ipautil.file_exists(replica_file):
+            sys.exit("Replica file %s does not exist" % replica_file)
+
+    # Initialize the ipalib api
+    cfg = dict(
+        in_server=True,
+        debug=options.debug,
+    )
+    api.bootstrap(**cfg)
+    api.finalize()
+
+    print "==================================================================="
+    print "This program will setup Dogtag DRM for the FreeIPA Server."
+    print ""
+
+    if not installing_replica:
+        drm = drminstance.DRMInstance(
+            realm_name,
+            dogtag_constants=dogtag.install_constants)
+
+        drm.configure_instance(host_name, domain_name, dm_password,
+                               dm_password, subject_base=subject)
+    else:
+        replica_config = create_replica_config(dm_password,
+                                               replica_file,
+                                               options)
+
+        if not read_replica_info_drm_enabled(replica_config.dir):
+            sys.exit("Either DRM is not installed on the master system or "
+                     "your replica file is out of date")
+
+        drm = drminstance.install_replica_drm(replica_config)
+        service.print_msg("Restarting the directory server")
+
+        ds = dsinstance.DsInstance()
+        ds.restart()
+
+    drm.enable_client_auth_to_db(drm.dogtag_constants.DRM_CS_CFG_PATH)
+
+    # Restart apache for new proxy config file
+    services.knownservices.httpd.restart(capture_output=True)
+
+    try:
+        with open("/etc/ipa/default.conf", "a") as fd:
+            fd.write("drm_enabled=True")
+    except IOError, e:
+        print "Failed to update /etc/ipa/default.conf"
+        root_logger.error(str(e))
+        sys.exit(1)
+
+
+fail_message = '''
+Your system may be partly configured.
+Run /usr/sbin/ipa-drm-install --uninstall to clean up.
+'''
+
+if __name__ == '__main__':
+    with installutils.private_ccache():
+        installutils.run_script(main, log_file_name=log_file_name,
+                                operation_name='ipa-drm-add',
+                                fail_message=fail_message)
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 5bfd61ee69d4682823a57f4b99a0d9a054a56d22..44eef7198cb8eec503ebf2cccc4ddc625d542d76 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -36,11 +36,11 @@ from ipaserver.install import bindinstance, httpinstance, ntpinstance
 from ipaserver.install import memcacheinstance
 from ipaserver.install import otpdinstance
 from ipaserver.install.replication import replica_conn_check, ReplicationManager
-from ipaserver.install.installutils import (ReplicaConfig, expand_replica_info,
-        read_replica_info, get_host_name, BadHostError, private_ccache,
-        read_replica_info_dogtag_port)
+from ipaserver.install.installutils import (private_ccache,
+        read_replica_info_drm_enabled, create_replica_config)
 from ipaserver.plugins.ldap2 import ldap2
 from ipaserver.install import cainstance
+from ipaserver.install import drminstance
 from ipalib import api, errors, util
 from ipalib.constants import CACERT
 from ipapython import version
@@ -65,6 +65,8 @@ def parse_options():
     basic_group = OptionGroup(parser, "basic options")
     basic_group.add_option("--setup-ca", dest="setup_ca", action="store_true",
                       default=False, help="configure a dogtag CA")
+    basic_group.add_option("--setup-drm", dest="setup_drm", action="store_true",
+                      default=False, help="configure a dogtag DRM")
     basic_group.add_option("--ip-address", dest="ip_address",
                       type="ip", ip_local=True,
                       help="Replica server IP Address")
@@ -505,44 +507,22 @@ def main():
         if dirman_password is None:
             sys.exit("Directory Manager password required")
 
-    try:
-        top_dir, dir = expand_replica_info(filename, dirman_password)
-        global REPLICA_INFO_TOP_DIR
-        REPLICA_INFO_TOP_DIR = top_dir
-    except Exception, e:
-        print "ERROR: Failed to decrypt or open the replica file."
-        print "Verify you entered the correct Directory Manager password."
-        sys.exit(1)
-
-    config = ReplicaConfig()
-    read_replica_info(dir, config)
-    root_logger.debug('Installing replica file with version %d (0 means no version in prepared file).' % config.version)
-    if config.version and config.version > version.NUM_VERSION:
-        root_logger.error('A replica file from a newer release (%d) cannot be installed on an older version (%d)' % (config.version, version.NUM_VERSION))
-        sys.exit(1)
-    config.dirman_password = dirman_password
-    try:
-        host = get_host_name(options.no_host_dns)
-    except BadHostError, e:
-        root_logger.error(str(e))
-        sys.exit(1)
-    if config.host_name != host:
-        try:
-            print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host)
-            if not ipautil.user_input("This may cause problems. Continue?", False):
-                sys.exit(0)
-            config.host_name = host
-            print ""
-        except KeyboardInterrupt:
-            sys.exit(0)
-    config.dir = dir
+    config = create_replica_config(dirman_password, filename, options)
     config.setup_ca = options.setup_ca
-    config.ca_ds_port = read_replica_info_dogtag_port(config.dir)
 
     if config.setup_ca and not ipautil.file_exists(config.dir + "/cacert.p12"):
         print 'CA cannot be installed in CA-less setup.'
         sys.exit(1)
 
+    config.setup_drm = options.setup_drm
+    if config.setup_drm:
+        if not config.setup_ca:
+            print "CA must be installed with the KRA"
+            sys.exit(1)
+        if not read_replica_info_drm_enabled(config.dir):
+            print "DRM is not installed on the master system"
+            sys.exit(1)
+
     installutils.verify_fqdn(config.master_host_name, options.no_host_dns)
 
     # check connection
@@ -576,6 +556,9 @@ def main():
         else:
             fd.write("enable_ra=False\n")
             fd.write("ra_plugin=none\n")
+
+        fd.write("enable_drm=%s\n" % config.setup_drm)
+
         fd.write("mode=production\n")
         fd.close()
     finally:
@@ -606,7 +589,7 @@ def main():
 
         # Check that we don't already have a replication agreement
         try:
-            (agreement_cn, agreement_dn) = replman.agreement_dn(host)
+            (agreement_cn, agreement_dn) = replman.agreement_dn(config.host_name)
             entry = conn.get_entry(agreement_dn, ['*'])
         except errors.NotFound:
             pass
@@ -616,20 +599,20 @@ def main():
             print ('A replication agreement for this host already exists. '
                    'It needs to be removed.')
             print "Run this on the master that generated the info file:"
-            print "    %% ipa-replica-manage del %s --force" % host
+            print "    %% ipa-replica-manage del %s --force" % config.host_name
             exit(3)
 
         # Check pre-existing host entry
         try:
-            entry = conn.find_entries(u'fqdn=%s' % host, ['fqdn'], DN(api.env.container_host, api.env.basedn))
+            entry = conn.find_entries(u'fqdn=%s' % config.host_name, ['fqdn'], DN(api.env.container_host, api.env.basedn))
         except errors.NotFound:
             pass
         else:
             root_logger.info(
-                'Error: Host %s already exists on the master server.' % host)
-            print 'The host %s already exists on the master server.' % host
+                'Error: Host %s already exists on the master server.' % config.host_name)
+            print 'The host %s already exists on the master server.' % config.host_name
             print "You should remove it before proceeding:"
-            print "    %% ipa host-del %s" % host
+            print "    %% ipa host-del %s" % config.host_name
             exit(3)
 
         # If remote host has DNS, check forward/reverse resolution
@@ -686,7 +669,7 @@ def main():
                        ipautil.realm_to_suffix(config.realm_name))
 
         # This is done within stopped_service context, which restarts CA
-        CA.enable_client_auth_to_db()
+        CA.enable_client_auth_to_db(CA.dogtag_constants.CS_CFG_PATH)
 
     krb = install_krb(config, setup_pkinit=options.setup_pkinit)
     http = install_http(config, auto_redirect=options.ui_redirect)
@@ -697,7 +680,7 @@ def main():
 
     if CA:
         CA.configure_certmonger_renewal()
-        CA.import_ra_cert(dir + "/ra.p12")
+        CA.import_ra_cert(config.dir + "/ra.p12")
         CA.fix_ra_perms()
         services.knownservices.httpd.restart()
 
@@ -709,9 +692,14 @@ def main():
     service.print_msg("Applying LDAP updates")
     ds.apply_updates()
 
-    # Restart ds and krb after configurations have been changed
-    service.print_msg("Restarting the directory server")
-    ds.restart()
+    if options.setup_drm:
+        drm = drminstance.install_replica_drm(config)
+        service.print_msg("Restarting the directory server")
+        ds.restart()
+        drm.enable_client_auth_to_db(drm.dogtag_constants.DRM_CS_CFG_PATH)
+    else:
+        service.print_msg("Restarting the directory server")
+        ds.restart()
 
     service.print_msg("Restarting the KDC")
     krb.restart()
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index 671a226d625ab9e8168c569a6d83c35dfae52115..d669190fdcea4b11dd5f4e34688dd5b193cc2490 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -3,7 +3,7 @@
 #          Simo Sorce <ssorce@redhat.com>
 #          Rob Crittenden <rcritten@redhat.com>
 #
-# Copyright (C) 2007-2010  Red Hat
+# Copyright (C) 2007-2014  Red Hat
 # see file 'COPYING' for use and warranty information
 #
 # This program is free software; you can redistribute it and/or modify
@@ -53,6 +53,7 @@ from ipaserver.install import httpinstance
 from ipaserver.install import ntpinstance
 from ipaserver.install import certs
 from ipaserver.install import cainstance
+from ipaserver.install import drminstance
 from ipaserver.install import memcacheinstance
 from ipaserver.install import otpdinstance
 from ipaserver.install import sysupgrade
@@ -494,11 +495,20 @@ def uninstall():
             dogtag_constants=dogtag_constants)
         if cads_instance.is_configured():
             cads_instance.uninstall()
-    cainstance.stop_tracking_certificates(dogtag_constants)
+
+    drm_instance = drminstance.DRMInstance(
+        api.env.realm, dogtag_constants=dogtag_constants)
+    drm_instance.stop_tracking_certificates(dogtag_constants)
+    if drm_instance.is_installed():
+        drm_instance.uninstall()
+
     ca_instance = cainstance.CAInstance(
         api.env.realm, certs.NSS_DIR, dogtag_constants=dogtag_constants)
+    ca_instance.stop_tracking_certificates(dogtag_constants)
+    ca_instance.stop_tracking_agent_certificate(dogtag_constants)
     if ca_instance.is_configured():
         ca_instance.uninstall()
+
     bindinstance.BindInstance(fstore).uninstall()
     httpinstance.HTTPInstance(fstore).uninstall()
     krbinstance.KrbInstance(fstore).uninstall()
@@ -755,8 +765,13 @@ def main():
     # We only set up the CA if the PKCS#12 options are not given.
     if options.dirsrv_pkcs12:
         setup_ca = False
+        setup_drm = False
     else:
         setup_ca = True
+        #  setup_drm is set to False until Dogtag 10.2 is available for IPA to consume
+        #  Until then users that want to install the DRM need to use ipa-install-drm
+        # TODO set setup_drm = True when Dogtag 10.2 is available
+        setup_drm = False
 
     # Figure out what external CA step we're in. See cainstance.py for more
     # info on the 3 states.
@@ -773,6 +788,8 @@ def main():
     print "This includes:"
     if setup_ca:
         print "  * Configure a stand-alone CA (dogtag) for certificate management"
+    if setup_drm:
+        print "  * Configure a stand-alone DRM (dogtag) for key storage"
     if options.conf_ntp:
         print "  * Configure the Network Time Daemon (ntpd)"
     print "  * Create and configure an instance of Directory Server"
@@ -1019,6 +1036,7 @@ def main():
     else:
         fd.write("enable_ra=False\n")
         fd.write("ra_plugin=none\n")
+    fd.write("enable_drm=%s\n" % setup_drm)
     fd.write("mode=production\n")
     fd.close()
 
@@ -1120,7 +1138,7 @@ def main():
                        ipautil.realm_to_suffix(realm_name), ['caRenewalMaster'])
 
         # This is done within stopped_service context, which restarts CA
-        ca.enable_client_auth_to_db()
+        ca.enable_client_auth_to_db(ca.dogtag_constants.CS_CFG_PATH)
 
     krb = krbinstance.KrbInstance(fstore)
     if options.pkinit_pkcs12:
@@ -1197,6 +1215,16 @@ def main():
     service.print_msg("Restarting the web server")
     http.restart()
 
+    if setup_drm:
+        drm = drminstance.DRMInstance(realm_name,
+            dogtag_constants=dogtag.install_constants)
+        drm.configure_instance(host_name, domain_name, dm_password,
+                               dm_password, subject_base=options.subject)
+
+        # This is done within stopped_service context, which restarts DRM
+        ds.restart()
+        drm.enable_client_auth_to_db(drm.dogtag_constants.DRM_CS_CFG_PATH)
+
     # Set the admin user kerberos password
     ds.change_admin_password(admin_password)
 
@@ -1249,9 +1277,10 @@ def main():
 
     print ""
     if setup_ca:
-        print "Be sure to back up the CA certificate stored in /root/cacert.p12"
-        print "This file is required to create replicas. The password for this"
-        print "file is the Directory Manager password"
+        print "Be sure to back up the CA certificates stored in /root/cacert.p12"
+        print "and the DRM certificates stored in /root/drmcert.p12"
+        print "These files are required to create replicas. The password for these"
+        print "files is the Directory Manager password"
     else:
         print "In order for Firefox autoconfiguration to work you will need to"
         print "use a SSL signing certificate. See the IPA documentation for more details."
diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
index 2fecc14042ae9462fe6bde79578c27ce97425b57..cba6d6c6d38a817f6fcc2f684b95eb3336b28dcd 100644
--- a/install/tools/ipa-upgradeconfig
+++ b/install/tools/ipa-upgradeconfig
@@ -541,7 +541,7 @@ def named_update_gssapi_configuration():
                                               bindinstance.NAMED_SECTION_OPTIONS)
         bindinstance.named_conf_set_directive('tkey-domain', None,
                                               bindinstance.NAMED_SECTION_OPTIONS)
-        bindinstance.named_conf_set_directive('tkey-gssapi-keytab', paths.NAMED_KEYTAB,
+        bindinstance.named_conf_set_directive('tkey-gssapi-keytab', '/etc/named.keytab',
                                               bindinstance.NAMED_SECTION_OPTIONS)
     except IOError, e:
         root_logger.error('Cannot update GSSAPI configuration in %s: %s',
@@ -703,7 +703,7 @@ def certificate_renewal_update(ca):
 
     # Ok, now we need to stop tracking, then we can start tracking them
     # again with new configuration:
-    cainstance.stop_tracking_certificates(dogtag_constants)
+    ca.stop_tracking_certificates(dogtag_constants)
 
     if not sysupgrade.get_upgrade_state('dogtag',
                                         'certificate_renewal_update_1'):
@@ -852,66 +852,7 @@ def find_subject_base():
     3) Last resort, look in the certmap.conf itself
     4) If all fails, log loudly and return None
     """
-    root_logger.debug('Trying to find certificate subject base in sysupgrade')
-    subject_base = sysupgrade.get_upgrade_state('certmap.conf', 'subject_base')
-
-    if subject_base:
-        root_logger.debug(
-            'Found certificate subject base in sysupgrade: %s',
-            subject_base
-        )
-        return subject_base
-
-    root_logger.debug('Unable to find certificate subject base in sysupgrade')
-    root_logger.debug('Trying to find certificate subject base in DS')
-
-    ds_is_running = services.knownservices.dirsrv.is_running()
-    if not ds_is_running:
-        try:
-            services.knownservices.dirsrv.start()
-        except ipautil.CalledProcessError as e:
-            root_logger.error('Cannot start DS to find certificate '
-                              'subject base: %s', e)
-        else:
-            ds_is_running = True
-
-    if ds_is_running:
-        try:
-            api.Backend.ldap2.connect(autobind=True)
-        except ipalib.errors.PublicError, e:
-            root_logger.error('Cannot connect to DS to find certificate '
-                              'subject base: %s', e)
-        else:
-            ret = api.Command['config_show']()
-            api.Backend.ldap2.disconnect()
-            subject_base = str(ret['result']['ipacertificatesubjectbase'][0])
-            root_logger.debug(
-                'Found certificate subject base in DS: %s',
-                subject_base
-            )
-
-    if not subject_base:
-        root_logger.debug('Unable to find certificate subject base in DS')
-        root_logger.debug('Trying to find certificate subject base in '
-                          'certmap.conf')
-
-        certmap_dir = dsinstance.config_dirname(
-            dsinstance.realm_to_serverid(api.env.realm)
-        )
-        try:
-            with open(os.path.join(certmap_dir, 'certmap.conf')) as f:
-                for line in f:
-                    if line.startswith('certmap ipaca'):
-                        subject_base = line.strip().split(',')[-1]
-                        root_logger.debug(
-                            'Found certificate subject base in certmap.conf: '
-                            '%s',
-                            subject_base
-                        )
-
-        except IOError as e:
-            root_logger.error('Cannot open certmap.conf to find certificate '
-                              'subject base: %s', e.strerror)
+    subject_base =  dsinstance.DsInstance().find_subject_base()
 
     if subject_base:
         sysupgrade.set_upgrade_state(
@@ -921,8 +862,6 @@ def find_subject_base():
         )
         return subject_base
 
-    root_logger.debug('Unable to find certificate subject base in '
-                      'certmap.conf')
     root_logger.error('Unable to determine certificate subject base. '
                       'certmap.conf will not be updated.')
 
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index 178d2942b8a33b5927c371f69e27997d98846ee2..7975416bf14424e76831872f74105adaa4a7d0d3 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -24,10 +24,9 @@ import ConfigParser
 from urllib import urlencode
 
 import nss.nss as nss
-from nss.error import NSPRError
 
 from ipalib import api, errors
-from ipalib.errors import NetworkError, CertificateOperationError
+from ipalib.errors import NetworkError
 from ipalib.text import _
 from ipapython import nsslib, ipautil
 from ipaplatform.paths import paths
@@ -42,6 +41,7 @@ from ipapython.ipa_log_manager import *
 # The configured_constants() function below provides constants relevant to
 # the configured version.
 
+
 class Dogtag10Constants(object):
     DOGTAG_VERSION = 10
     UNSECURE_PORT = 8080
@@ -62,6 +62,7 @@ class Dogtag10Constants(object):
     PASSWORD_CONF_PATH = '%s/conf/password.conf' % PKI_ROOT
     SERVICE_PROFILE_DIR = '%s/ca/profiles/ca' % PKI_ROOT
     ALIAS_DIR = paths.PKI_TOMCAT_ALIAS_DIR.rstrip('/')
+    DRM_CS_CFG_PATH = '%s/conf/kra/CS.cfg' % PKI_ROOT
 
     SERVICE_NAME = 'pki_tomcatd'
 
@@ -163,7 +164,8 @@ def get_ca_certchain(ca_host=None, dogtag_constants=None):
     if dogtag_constants is None:
         dogtag_constants = configured_constants()
     chain = None
-    conn = httplib.HTTPConnection(ca_host,
+    conn = httplib.HTTPConnection(
+        ca_host,
         api.env.ca_install_port or dogtag_constants.UNSECURE_PORT)
     conn.request("GET", "/ca/ee/ca/getCertChain")
     res = conn.getresponse()
@@ -242,7 +244,7 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
 
     body = urlencode(kw)
     return _httplib_request(
-            'https', host, port, url, connection_factory, body)
+        'https', host, port, url, connection_factory, body)
 
 
 def http_request(host, port, url, **kw):
@@ -289,7 +291,8 @@ def _httplib_request(
     root_logger.debug('request body %r', request_body)
     try:
         conn = connection_factory(host, port)
-        conn.request('POST', uri,
+        conn.request(
+            'POST', uri,
             body=request_body,
             headers={'Content-type': 'application/x-www-form-urlencoded'},
         )
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index b13a77d5811343175288c1191991f1ee6e6b721a..6e4d500294ef0ef928969d46da4f9dfb98354900 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -19,53 +19,50 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import pwd
+import array
+import base64
+import binascii
+import ConfigParser
+import dbus
+import httplib
+import ldap
 import os
-import sys
+import pwd
 import re
+import shutil
+import stat
+import subprocess
+import sys
+import syslog
 import time
-import ldap
-import base64
-import array
 import tempfile
-import binascii
-import shutil
-import httplib
 import urllib
 import xml.dom.minidom
-import stat
-import syslog
-import ConfigParser
-import dbus
 
-from ipapython import dogtag
-from ipapython.certdb import get_ca_nickname
-from ipapython import certmonger
 from ipalib import api
 from ipalib import pkcs10, x509
 from ipalib import errors
-from ipapython.dn import DN
-import subprocess
-import traceback
 
+from ipaplatform import services
+from ipaplatform.paths import paths
+from ipaplatform.tasks import tasks
+
+from ipapython import dogtag
+from ipapython import certmonger
 from ipapython import ipautil
 from ipapython import ipaldap
-from ipaplatform.tasks import tasks
-from ipaserver.install import service
-from ipaserver.install import installutils
-from ipaserver.install import dsinstance
-from ipaserver.install import certs
-from ipaserver.install.installutils import stopped_service
-from ipaserver.plugins import ldap2
+from ipapython.certdb import get_ca_nickname
+from ipapython.dn import DN
 from ipapython.ipa_log_manager import *
-from ipaplatform import services
-from ipaplatform.paths import paths
 
-HTTPD_CONFD = paths.HTTPD_CONF_D_DIR
-DEFAULT_DSPORT = dogtag.install_constants.DS_PORT
+from ipaserver.install import certs
+from ipaserver.install import dsinstance
+from ipaserver.install import installutils
+from ipaserver.install import service
+from ipaserver.install.dogtaginstance import DogtagInstance
+from ipaserver.install.dogtaginstance import PKI_USER, DEFAULT_DSPORT
+from ipaserver.plugins import ldap2
 
-PKI_USER = "pkiuser"
-PKI_DS_USER = dogtag.install_constants.DS_USER
 
 # When IPA is installed with DNS support, this CNAME should hold all IPA
 # replicas with CA configured
@@ -88,22 +85,6 @@ RootDNPwd= $PASSWORD
 ConfigFile = /usr/share/pki/ca/conf/database.ldif
 """
 
-def check_inst():
-    """
-    Validate that the appropriate dogtag/RHCS packages have been installed.
-    """
-
-    # Check for a couple of binaries we need
-    if not os.path.exists(dogtag.install_constants.SPAWN_BINARY):
-        return False
-    if not os.path.exists(dogtag.install_constants.DESTROY_BINARY):
-        return False
-
-    # This is the template tomcat file for a CA
-    if not os.path.exists(paths.PKI_CONF_SERVER_XML):
-        return False
-
-    return True
 
 def get_preop_pin(instance_root, instance_name):
     # Only used for Dogtag 9
@@ -113,17 +94,17 @@ def get_preop_pin(instance_root, instance_name):
 
     # read the config file and get the preop pin
     try:
-        f=open(filename)
+        f = open(filename)
     except IOError, e:
         root_logger.error("Cannot open configuration file." + str(e))
         raise e
     data = f.read()
     data = data.split('\n')
-    pattern = re.compile("preop.pin=(.*)" )
+    pattern = re.compile("preop.pin=(.*)")
     for line in data:
         match = re.search(pattern, line)
-        if (match):
-            preop_pin=match.group(1)
+        if match:
+            preop_pin = match.group(1)
             break
 
     if preop_pin is None:
@@ -131,6 +112,7 @@ def get_preop_pin(instance_root, instance_name):
 
     return preop_pin
 
+
 def import_pkcs12(input_file, input_passwd, cert_database,
                   cert_passwd):
     ipautil.run([paths.PK12UTIL, "-d", cert_database,
@@ -138,21 +120,23 @@ def import_pkcs12(input_file, input_passwd, cert_database,
                  "-k", cert_passwd,
                  "-w", input_passwd])
 
+
 def get_value(s):
     """
     Parse out a name/value pair from a Javascript variable.
     """
     try:
-        expr = s.split('=',1)
+        expr = s.split('=', 1)
         value = expr[1]
         value = value.replace('\"', '')
-        value = value.replace(';','')
-        value = value.replace('\\n','\n')
-        value = value.replace('\\r','\r')
+        value = value.replace(';', '')
+        value = value.replace('\\n', '\n')
+        value = value.replace('\\r', '\r')
         return value
     except IndexError:
         return None
 
+
 def find_substring(data, value):
     """
     Scan through a list looking for a string that starts with value.
@@ -161,6 +145,7 @@ def find_substring(data, value):
         if d.startswith(value):
             return get_value(d)
 
+
 def get_defList(data):
     """
     Return a dictionary of defList name/value pairs.
@@ -196,6 +181,7 @@ def get_defList(data):
 
     return defdict
 
+
 def get_outputList(data):
     """
     Return a dictionary of outputList name/value pairs.
@@ -221,6 +207,7 @@ def get_outputList(data):
 
     return outputdict
 
+
 def get_crl_files(path=None):
     """
     Traverse dogtag's CRL files in default CRL publish directory or in chosen
@@ -240,8 +227,8 @@ def get_crl_files(path=None):
 
 
 def is_step_one_done():
-    '''Read CS.cfg and determine if step one of an external CA install is done
-    '''
+    """Read CS.cfg and determine if step one of an external CA install is done
+    """
     path = dogtag.install_constants.CS_CFG_PATH
     if not os.path.exists(path):
         return False
@@ -258,7 +245,8 @@ class CADSInstance(service.Service):
     Thus this class only does uninstallation.
     """
     def __init__(self, host_name=None, realm_name=None, domain_name=None, dm_password=None, dogtag_constants=None):
-        service.Service.__init__(self, "pkids",
+        service.Service.__init__(
+            self, "pkids",
             service_desc="directory server for the CA",
             dm_password=dm_password,
             ldapi=False,
@@ -284,7 +272,7 @@ class CADSInstance(service.Service):
         serverid = self.restore_state("serverid")
 
         # Just eat this state if it exists
-        running = self.restore_state("running")
+        _running = self.restore_state("running")
 
         if not enabled is None and not enabled:
             services.knownservices.dirsrv.disable()
@@ -297,40 +285,14 @@ class CADSInstance(service.Service):
             dsdb.untrack_server_cert("Server-Cert")
             dsinstance.erase_ds_instance_data(serverid)
 
-        user_exists = self.restore_state("user_exists")
+        _user_exists = self.restore_state("user_exists")
 
         # At one time we removed this user on uninstall. That can potentially
-        # orphan files, or worse, if another useradd runs in the intermim,
+        # orphan files, or worse, if another useradd runs in the interim,
         # cause files to have a new owner.
 
 
-def stop_tracking_certificates(dogtag_constants):
-    """Stop tracking our certificates. Called on uninstall.
-    """
-    cmonger = services.knownservices.certmonger
-    services.knownservices.messagebus.start()
-    cmonger.start()
-
-    for nickname in ['Server-Cert cert-pki-ca',
-                        'auditSigningCert cert-pki-ca',
-                        'ocspSigningCert cert-pki-ca',
-                        'subsystemCert cert-pki-ca']:
-        try:
-            certmonger.stop_tracking(
-                dogtag_constants.ALIAS_DIR, nickname=nickname)
-        except (ipautil.CalledProcessError, RuntimeError), e:
-            root_logger.error(
-                "certmonger failed to stop tracking certificate: %s" % str(e))
-
-    try:
-        certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname='ipaCert')
-    except (ipautil.CalledProcessError, RuntimeError), e:
-        root_logger.error(
-            "certmonger failed to stop tracking certificate: %s" % str(e))
-    cmonger.stop()
-
-
-class CAInstance(service.Service):
+class CAInstance(DogtagInstance):
     """
     When using a dogtag CA the DS database contains just the
     server cert for DS. The mod_nss database will contain the RA agent
@@ -351,19 +313,8 @@ class CAInstance(service.Service):
         if dogtag_constants is None:
             dogtag_constants = dogtag.configured_constants()
 
-        service.Service.__init__(self,
-                '%sd' % dogtag_constants.PKI_INSTANCE_NAME,
-                service_desc="certificate server"
-                )
-
-        self.dogtag_constants = dogtag_constants
-        self.realm = realm
-        self.dm_password = None
-        self.admin_password = None
-        self.fqdn = None
-        self.domain = None
-        self.pkcs12_info = None
-        self.clone = False
+        DogtagInstance.__init__(self, realm, "CA", "certificate server",
+                                dogtag_constants)
 
         # for external CAs
         self.external = 0
@@ -372,32 +323,15 @@ class CAInstance(service.Service):
         self.cert_chain_file = None
         self.create_ra_agent_db = True
 
-        # The same database is used for mod_nss because the NSS context
-        # will already have been initialized by Apache by the time
-        # mod_python wants to do things.
         self.canickname = get_ca_nickname(realm)
-        self.basedn = DN(('o', 'ipaca'))
-        self.ca_agent_db = tempfile.mkdtemp(prefix = "tmp-")
         self.ra_agent_db = ra_db
         self.ra_agent_pwd = self.ra_agent_db + "/pwdfile.txt"
-        self.ds_port = DEFAULT_DSPORT
-        self.security_domain_name = "IPA"
-        self.server_root = dogtag_constants.SERVER_ROOT
         self.ra_cert = None
         self.requestId = None
-
-    def __del__(self):
-        shutil.rmtree(self.ca_agent_db, ignore_errors=True)
-
-    def is_installed(self):
-        """
-        Installing with an external CA is a two-step process. This
-        is used to determine if the first step has been done.
-
-        Returns True/False
-        """
-        return os.path.exists(os.path.join(
-            self.server_root, self.dogtag_constants.PKI_INSTANCE_NAME))
+        self.tracking_nicknames = ['Server-Cert cert-pki-ca',
+                                   'auditSigningCert cert-pki-ca',
+                                   'ocspSigningCert cert-pki-ca',
+                                   'subsystemCert cert-pki-ca']
 
     def configure_instance(self, host_name, domain, dm_password,
                            admin_password, ds_port=DEFAULT_DSPORT,
@@ -434,12 +368,12 @@ class CAInstance(service.Service):
         # Determine if we are installing as an externally-signed CA and
         # what stage we're in.
         if csr_file is not None:
-            self.csr_file=csr_file
-            self.external=1
+            self.csr_file = csr_file
+            self.external = 1
         elif cert_file is not None:
-            self.cert_file=cert_file
-            self.cert_chain_file=cert_chain_file
-            self.external=2
+            self.cert_file = cert_file
+            self.cert_chain_file = cert_chain_file
+            self.external = 2
 
         self.step("creating certificate server user", self.__create_ca_user)
         if self.dogtag_constants.DOGTAG_VERSION >= 10:
@@ -448,10 +382,10 @@ class CAInstance(service.Service):
             if not ipautil.dir_exists(paths.VAR_LIB_PKI_CA_DIR):
                 self.step("creating pki-ca instance", self.create_instance)
             self.step("configuring certificate server instance", self.__configure_instance)
-        self.step("stopping certificate server instance to update CS.cfg", self.__stop)
+        self.step("stopping certificate server instance to update CS.cfg", self.stop_instance)
         self.step("disabling nonces", self.__disable_nonce)
         self.step("set up CRL publishing", self.__enable_crl_publish)
-        self.step("starting certificate server instance", self.__start)
+        self.step("starting certificate server instance", self.start_instance)
         # Step 1 of external is getting a CSR so we don't need to do these
         # steps until we get a cert back from the external CA.
         if self.external != 1:
@@ -467,33 +401,43 @@ class CAInstance(service.Service):
             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)
+            self.step("configuring certificate server to start on boot",
+                      self.enable)
             if not self.clone:
-                self.step("restarting certificate server", self.__restart_instance)
+                self.step("restarting certificate server", self.restart_instance)
                 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("configure certmonger for renewals", self.configure_certmonger_renewal)
-            self.step("configure certificate renewals", self.configure_renewal)
+            self.step("configure certificate renewals", self.configure_cert_renewal)
             if not self.clone:
                 self.step("configure RA certificate renewal", self.configure_agent_renewal)
             self.step("configure Server-Cert certificate renewal", self.track_servercert)
-            self.step("Configure HTTP to proxy connections", self.__http_proxy)
+            self.step("Configure HTTP to proxy connections",
+                      self.http_proxy)
 
         self.start_creation(runtime=210)
 
-    def __stop(self):
-        self.stop()
+    def enable(self):
+        DogtagInstance.enable(self)
+
+    def start_instance(self):
+        DogtagInstance.start_instance(self)
 
-    def __start(self):
-        self.start()
+    def stop_instance(self):
+        DogtagInstance.stop_instance(self)
 
+    def restart_instance(self):
+        DogtagInstance.restart_instance(self)
+
+    def http_proxy(self):
+        DogtagInstance.http_proxy(self)
 
     def __spawn_instance(self):
         """
         Create and configure a new CA instance using pkispawn.
-        pkispawn requires a configuration file with IPA-specific
-        parameters.
+        Creates the config file with IPA specific parameters
+        and passes it to the base class to call pkispawn
         """
 
         # Create an empty and secured file
@@ -515,7 +459,7 @@ class CAInstance(service.Service):
         config.set("CA", "pki_backup_password", self.admin_password)
 
         # Client security database
-        config.set("CA", "pki_client_database_dir", self.ca_agent_db)
+        config.set("CA", "pki_client_database_dir", self.agent_db)
         config.set("CA", "pki_client_database_password", self.admin_password)
         config.set("CA", "pki_client_database_purge", "False")
         config.set("CA", "pki_client_pkcs12_password", self.admin_password)
@@ -555,7 +499,7 @@ class CAInstance(service.Service):
         config.set("CA", "pki_audit_signing_nickname", "auditSigningCert cert-pki-ca")
         config.set("CA", "pki_ca_signing_nickname", "caSigningCert cert-pki-ca")
 
-        if (self.clone):
+        if self.clone:
             cafile = self.pkcs12_info[0]
             shutil.copy(cafile, paths.TMP_CA_P12)
             pent = pwd.getpwnam(PKI_USER)
@@ -592,21 +536,8 @@ class CAInstance(service.Service):
         with open(cfg_file, "wb") as f:
             config.write(f)
 
-        # Define the things we don't want logged
-        nolog = (self.admin_password, self.dm_password,)
-
-        args = [paths.PKISPAWN, "-s", "CA", "-f", cfg_file ]
-
-        with open(cfg_file) as f:
-            root_logger.debug(
-                'Contents of pkispawn configuration file (%s):\n%s' %
-                    (cfg_file, ipautil.nolog_replace(f.read(), nolog)))
-
         try:
-            ipautil.run(args, nolog=nolog)
-        except ipautil.CalledProcessError, e:
-            root_logger.critical("failed to configure ca instance %s" % e)
-            raise RuntimeError('Configuration of CA failed')
+            DogtagInstance.spawn_instance(self, cfg_file)
         finally:
             os.remove(cfg_file)
 
@@ -615,7 +546,7 @@ class CAInstance(service.Service):
             print "ipa-server-install --external_cert_file=/path/to/signed_certificate --external_ca_file=/path/to/external_ca_certificate"
             sys.exit(0)
         else:
-            shutil.move(paths.CA_BACKUP_KEYS_P12, \
+            shutil.move(paths.CA_BACKUP_KEYS_P12,
                         paths.CACERT_P12)
 
         root_logger.debug("completed creating ca instance")
@@ -648,15 +579,8 @@ class CAInstance(service.Service):
         ]
         ipautil.run(args, env={'PKI_HOSTNAME':self.fqdn})
 
-    def __enable(self):
-        self.backup_state("enabled", self.is_enabled())
-        # We do not let the system start IPA components on its own,
-        # Instead we reply on the IPA init script to start only enabled
-        # components as found in our LDAP configuration tree
-        # We need to install DS before we can actually ldap_enable a service.
-        # so actual enablement is delayed.
-
-    def __create_ca_user(self):
+    @staticmethod
+    def __create_ca_user():
         try:
             pwd.getpwnam(PKI_USER)
             root_logger.debug("ca user %s exists" % PKI_USER)
@@ -681,7 +605,7 @@ class CAInstance(service.Service):
             args = [paths.PERL, paths.PKISILENT,  "ConfigureCA",
                     "-cs_hostname", self.fqdn,
                     "-cs_port", str(self.dogtag_constants.ADMIN_SECURE_PORT),
-                    "-client_certdb_dir", self.ca_agent_db,
+                    "-client_certdb_dir", self.agent_db,
                     "-client_certdb_pwd", self.admin_password,
                     "-preop_pin" , preop_pin,
                     "-domain_name", self.security_domain_name,
@@ -726,7 +650,7 @@ class CAInstance(service.Service):
             else:
                 args.append("-external")
                 args.append("false")
-            if (self.clone):
+            if self.clone:
                 """sd = security domain -->  all CS systems get registered to
                    a security domain. This is set to the hostname and port of
                    the master CA.
@@ -780,14 +704,6 @@ class CAInstance(service.Service):
 
         root_logger.debug("completed creating ca instance")
 
-    def __restart_instance(self):
-        try:
-            self.restart(self.dogtag_constants.PKI_INSTANCE_NAME)
-        except Exception:
-            # TODO: roll back here?
-            root_logger.debug(traceback.format_exc())
-            root_logger.critical("Failed to restart the certificate server. See the installation log for details.")
-
     def __disable_nonce(self):
         # Turn off Nonces
         update_result = installutils.update_file(
@@ -805,9 +721,9 @@ class CAInstance(service.Service):
         os.write(admin_fd, self.admin_password)
         os.close(admin_fd)
 
-        # Look thru the cert chain to get all the certs we need to add
+        # Look through the cert chain to get all the certs we need to add
         # trust for
-        p = subprocess.Popen([paths.CERTUTIL, "-d", self.ca_agent_db,
+        p = subprocess.Popen([paths.CERTUTIL, "-d", self.agent_db,
                               "-O", "-n", "ipa-ca-agent"], stdout=subprocess.PIPE)
 
         chain = p.stdout.read()
@@ -826,7 +742,7 @@ class CAInstance(service.Service):
                 self.__run_certutil(
                     ['-M', '-t', 'CT,C,C', '-n',
                      nick],
-                     database=self.ca_agent_db, pwd_file=self.admin_password)
+                     database=self.agent_db, pwd_file=self.admin_password)
         finally:
             os.remove(admin_name)
 
@@ -842,12 +758,13 @@ class CAInstance(service.Service):
             '-v',
             '-n', 'ipa-ca-agent',
             '-p', self.admin_password,
-            '-d', self.ca_agent_db,
+            '-d', self.agent_db,
             '-r', '/ca/agent/ca/profileReview?requestId=%s' % self.requestId,
             '%s' % ipautil.format_netloc(
                 self.fqdn, self.dogtag_constants.AGENT_SECURE_PORT),
         ]
-        (stdout, stderr, returncode) = ipautil.run(args, nolog=(self.admin_password,))
+        (stdout, _stderr, _returncode) = ipautil.run(
+            args, nolog=(self.admin_password,))
 
         data = stdout.split(self.dogtag_constants.RACERT_LINE_SEP)
         params = get_defList(data)
@@ -863,13 +780,14 @@ class CAInstance(service.Service):
             '-v',
             '-n', 'ipa-ca-agent',
             '-p', self.admin_password,
-            '-d', self.ca_agent_db,
+            '-d', self.agent_db,
             '-e', params,
             '-r', '/ca/agent/ca/profileProcess',
             '%s' % ipautil.format_netloc(
                 self.fqdn, self.dogtag_constants.AGENT_SECURE_PORT),
         ]
-        (stdout, stderr, returncode) = ipautil.run(args, nolog=(self.admin_password,))
+        (stdout, _stderr, _returncode) = ipautil.run(
+            args, nolog=(self.admin_password,))
 
         data = stdout.split(self.dogtag_constants.RACERT_LINE_SEP)
         outputList = get_outputList(data)
@@ -948,7 +866,7 @@ class CAInstance(service.Service):
 
         conn.unbind()
 
-    def __run_certutil(self, args, database=None, pwd_file=None,stdin=None):
+    def __run_certutil(self, args, database=None, pwd_file=None, stdin=None):
         if not database:
             database = self.ra_agent_db
         if not pwd_file:
@@ -974,7 +892,7 @@ class CAInstance(service.Service):
         os.close(f)
         os.chmod(self.ra_agent_pwd, stat.S_IRUSR)
 
-        (stdout, stderr, returncode)  = self.__run_certutil(["-N"])
+        (_stdout, _stderr, _returncode) = self.__run_certutil(["-N"])
 
     def __get_ca_chain(self):
         try:
@@ -991,8 +909,8 @@ class CAInstance(service.Service):
         try:
             ipautil.run([paths.PK12UTIL,
                          "-n", "ipa-ca-agent",
-                         "-o", paths.CA_AGENT_P12,
-                         "-d", self.ca_agent_db,
+                         "-o", DogtagInstance.AGENT_P12_PATH,
+                         "-d", self.agent_db,
                          "-k", pwd_name,
                          "-w", pwd_name])
         finally:
@@ -1010,14 +928,15 @@ class CAInstance(service.Service):
         # makes openssl throw up.
         data = base64.b64decode(chain)
 
-        (certlist, stderr, returncode) = ipautil.run([paths.OPENSSL,
+        (certlist, _stderr, _returncode) = ipautil.run(
+            [paths.OPENSSL,
              "pkcs7",
              "-inform",
              "DER",
              "-print_certs",
              ], stdin=data)
 
-        # Ok, now we have all the certificates in certs, walk thru it
+        # Ok, now we have all the certificates in certs, walk through it
         # and pull out each certificate and add it to our database
 
         st = 1
@@ -1032,7 +951,7 @@ class CAInstance(service.Service):
                     (chain_fd, chain_name) = tempfile.mkstemp()
                     os.write(chain_fd, certlist[st:en+25])
                     os.close(chain_fd)
-                    (rdn, subject_dn) = certs.get_cert_nickname(certlist[st:en+25])
+                    (_rdn, subject_dn) = certs.get_cert_nickname(certlist[st:en+25])
                     if subject_dn == ca_dn:
                         nick = get_ca_nickname(self.realm)
                     else:
@@ -1043,7 +962,7 @@ class CAInstance(service.Service):
                     )
                 finally:
                     os.remove(chain_name)
-                    subid = subid + 1
+                    subid += 1
 
     def __request_ra_certificate(self):
         # Create a noise file for generating our private key
@@ -1054,7 +973,10 @@ class CAInstance(service.Service):
 
         # Generate our CSR. The result gets put into stdout
         try:
-            (stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", str(DN(('CN', 'IPA RA'), self.subject_base)), "-z", noise_name, "-a"])
+            (stdout, _stderr, _returncode) = self.__run_certutil(
+                ["-R", "-k", "rsa", "-g", "2048", "-s",
+                 str(DN(('CN', 'IPA RA'), self.subject_base)),
+                 "-z", noise_name, "-a"])
         finally:
             os.remove(noise_name)
 
@@ -1271,73 +1193,30 @@ class CAInstance(service.Service):
                 'OU=pki-ipa, O=IPA', str(self.subject_base)):
             print "Updating subject_base in CA template failed"
 
-    def enable_client_auth_to_db(self):
-        """
-        Enable client auth connection to the internal db.
-        """
-        caconfig = dogtag.install_constants.CS_CFG_PATH
-
-        with stopped_service(self.dogtag_constants.SERVICE_NAME,
-                        instance_name=self.dogtag_constants.PKI_INSTANCE_NAME):
-
-            # Enable file publishing, disable LDAP
-            installutils.set_directive(caconfig,
-                'authz.instance.DirAclAuthz.ldap.ldapauth.authtype',
-                'SslClientAuth', quotes=False, separator='=')
-            installutils.set_directive(caconfig,
-                'authz.instance.DirAclAuthz.ldap.ldapauth.bindDN',
-                'uid=pkidbuser,ou=people,o=ipa-ca', quotes=False, separator='=')
-            installutils.set_directive(caconfig,
-                'authz.instance.DirAclAuthz.ldap.ldapauth.clientCertNickname',
-                'subsystemCert cert-pki-ca', quotes=False, separator='=')
-            installutils.set_directive(caconfig,
-                'authz.instance.DirAclAuthz.ldap.ldapconn.port',
-                str(dogtag.install_constants.DS_SECURE_PORT),
-                quotes=False, separator='=')
-            installutils.set_directive(caconfig,
-                'authz.instance.DirAclAuthz.ldap.ldapconn.secureConn',
-                'true', quotes=False, separator='=')
-
-            installutils.set_directive(caconfig, 'internaldb.ldapauth.authtype',
-                'SslClientAuth', quotes=False, separator='=')
-            installutils.set_directive(caconfig, 'internaldb.ldapauth.bindDN',
-                'uid=pkidbuser,ou=people,o=ipa-ca', quotes=False, separator='=')
-            installutils.set_directive(caconfig,
-                'internaldb.ldapauth.clientCertNickname',
-                'subsystemCert cert-pki-ca', quotes=False, separator='=')
-            installutils.set_directive(caconfig, 'internaldb.ldapconn.port',
-                str(dogtag.install_constants.DS_SECURE_PORT),
-                quotes=False, separator='=')
-            installutils.set_directive(caconfig,
-                 'internaldb.ldapconn.secureConn', 'true', quotes=False,
-                 separator='=')
-
     def uninstall(self):
-        if self.is_configured():
-            self.print_msg("Unconfiguring CA")
-
         enabled = self.restore_state("enabled")
         if not enabled is None and not enabled:
             self.disable()
 
-        try:
-            if self.dogtag_constants.DOGTAG_VERSION >= 10:
-                ipautil.run([paths.PKIDESTROY, "-i",
-                                self.dogtag_constants.PKI_INSTANCE_NAME,
-                             "-s", "CA"])
-            else:
-                ipautil.run([paths.PKIREMOVE,
+        if self.dogtag_constants.DOGTAG_VERSION >= 10:
+            DogtagInstance.uninstall(self)
+        else:
+            if self.is_configured():
+                self.print_msg("Unconfiguring CA")
+
+            try:
+                ipautil.run(["/usr/bin/pkiremove",
                              "-pki_instance_root=/var/lib",
                              "-pki_instance_name=%s" %
                                 self.dogtag_constants.PKI_INSTANCE_NAME,
                              "--force"])
-        except ipautil.CalledProcessError, e:
-            root_logger.critical("failed to uninstall CA instance %s" % e)
+            except ipautil.CalledProcessError, e:
+                root_logger.critical("failed to uninstall CA instance %s" % e)
 
         # At one time we removed this user on uninstall. That can potentially
-        # orphan files, or worse, if another useradd runs in the intermim,
+        # orphan files, or worse, if another useradd runs in the interim,
         # cause files to have a new owner.
-        user_exists = self.restore_state("user_exists")
+        _user_exists = self.restore_state("user_exists")
 
         services.knownservices.messagebus.start()
         cmonger = services.knownservices.certmonger
@@ -1370,24 +1249,14 @@ class CAInstance(service.Service):
 
     def publish_ca_cert(self, location):
         args = ["-L", "-n", self.canickname, "-a"]
-        (cert, err, returncode) = self.__run_certutil(args)
+        (cert, _err, _returncode) = self.__run_certutil(args)
         fd = open(location, "w+")
         fd.write(cert)
         fd.close()
         os.chmod(location, 0444)
 
-    def __http_proxy(self):
-        template_filename = ipautil.SHARE_DIR + "ipa-pki-proxy.conf"
-        sub_dict = dict(
-            DOGTAG_PORT=self.dogtag_constants.AJP_PORT,
-            CLONE='' if self.clone else '#',
-            FQDN=self.fqdn,
-        )
-        template = ipautil.template_file(template_filename, sub_dict)
-        with open(HTTPD_CONFD + "ipa-pki-proxy.conf", "w") as fd:
-            fd.write(template)
-
-    def configure_certmonger_renewal(self):
+    @staticmethod
+    def configure_certmonger_renewal():
         """
         Create a new CA type for certmonger that will retrieve updated
         certificates from the dogtag master server.
@@ -1407,7 +1276,8 @@ class CAInstance(service.Service):
                 'dogtag-ipa-ca-renew-agent',
                 paths.DOGTAG_IPA_CA_RENEW_AGENT_SUBMIT, [])
 
-    def configure_agent_renewal(self):
+    @staticmethod
+    def configure_agent_renewal():
         try:
             certmonger.dogtag_start_tracking(
                 ca='dogtag-ipa-ca-renew-agent',
@@ -1429,25 +1299,15 @@ class CAInstance(service.Service):
             raise RuntimeError(
                 'Unable to determine PIN for CA instance: %s' % e)
 
-    def configure_renewal(self):
-        pin = self.__get_ca_pin()
+    def configure_cert_renewal(self):
+        """
+        Configure system certificates for renewal.
+        """
+        nicknames = ['auditSigningCert cert-pki-ca',
+                     'ocspSigningCert cert-pki-ca',
+                     'subsystemCert cert-pki-ca']
 
-        # Server-Cert cert-pki-ca is renewed per-server
-        for nickname in ['auditSigningCert cert-pki-ca',
-                         'ocspSigningCert cert-pki-ca',
-                         'subsystemCert cert-pki-ca']:
-            try:
-                certmonger.dogtag_start_tracking(
-                    ca='dogtag-ipa-ca-renew-agent',
-                    nickname=nickname,
-                    pin=pin,
-                    pinfile=None,
-                    secdir=self.dogtag_constants.ALIAS_DIR,
-                    pre_command='stop_pkicad',
-                    post_command='renew_ca_cert "%s"' % nickname)
-            except (ipautil.CalledProcessError, RuntimeError), e:
-                root_logger.error(
-                    "certmonger failed to start tracking certificate: %s" % e)
+        DogtagInstance.configure_renewal(self, nicknames)
 
     def track_servercert(self):
         """
@@ -1469,6 +1329,20 @@ class CAInstance(service.Service):
             root_logger.error(
                 "certmonger failed to start tracking certificate: %s" % e)
 
+    @staticmethod
+    def stop_tracking_agent_certificate(dogtag_constants):
+        """Stop tracking agent certificate. Called on uninstall.
+        """
+        cmonger = services.knownservices.certmonger
+        services.knownservices.messagebus.start()
+        cmonger.start()
+        try:
+            certmonger.stop_tracking('/etc/httpd/alias', nickname='ipaCert')
+        except (ipautil.CalledProcessError, RuntimeError), e:
+            root_logger.error(
+                "certmonger failed to stop tracking certificate: %s" % str(e))
+        cmonger.stop()
+
     def enable_subject_key_identifier(self):
         """
         See if Subject Key Identifier is set in the profile and if not, add it.
@@ -1480,7 +1354,7 @@ class CAInstance(service.Service):
         # 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 = setlist + ',10'
+            setlist += ',10'
             installutils.set_directive(
                 self.dogtag_constants.IPA_SERVICE_PROFILE,
                 'policyset.serverCertSet.list',
@@ -1528,7 +1402,7 @@ class CAInstance(service.Service):
         # 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 = setlist + ',11'
+            setlist += ',11'
             installutils.set_directive(
                 self.dogtag_constants.IPA_SERVICE_PROFILE,
                 'policyset.serverCertSet.list',
@@ -1571,13 +1445,14 @@ class CAInstance(service.Service):
         """
         # Check the default validity period of the audit signing cert
         # and set it to 2 years if it is 6 months.
-        range = installutils.get_directive(
+        cert_range = installutils.get_directive(
             '%s/caSignedLogCert.cfg' % self.dogtag_constants.SERVICE_PROFILE_DIR,
             'policyset.caLogSigningSet.2.default.params.range',
             separator='='
         )
-        root_logger.debug('caSignedLogCert.cfg profile validity range is %s' % range)
-        if range == "180":
+        root_logger.debug(
+            'caSignedLogCert.cfg profile validity range is %s' % cert_range)
+        if cert_range == "180":
             installutils.set_directive(
                 '%s/caSignedLogCert.cfg' % self.dogtag_constants.SERVICE_PROFILE_DIR,
                 'policyset.caLogSigningSet.2.default.params.range',
@@ -1602,15 +1477,39 @@ class CAInstance(service.Service):
 
         dn = DN(('cn', 'CA'), ('cn', api.env.host), ('cn', 'masters'),
                 ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
-        filter = '(ipaConfigString=caRenewalMaster)'
+        renewal_filter = '(ipaConfigString=caRenewalMaster)'
         try:
-            self.admin_conn.get_entries(base_dn=dn, filter=filter,
+            self.admin_conn.get_entries(base_dn=dn, filter=renewal_filter,
                                         attrs_list=[])
         except errors.NotFound:
             return False
 
         return True
 
+    @staticmethod
+    def update_cert_config(nickname, cert, dogtag_constants=None):
+        """
+        When renewing a CA subsystem certificate the configuration file
+        needs to get the new certificate as well.
+
+        nickname is one of the known nicknames.
+        cert is a DER-encoded certificate.
+        """
+
+        if dogtag_constants is None:
+            dogtag_constants = dogtag.configured_constants()
+
+        # The cert directive to update per nickname
+        directives = {'auditSigningCert cert-pki-ca': 'ca.audit_signing.cert',
+                      'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert',
+                      'caSigningCert cert-pki-ca': 'ca.signing.cert',
+                      'subsystemCert cert-pki-ca': 'ca.subsystem.cert',
+                      'Server-Cert cert-pki-ca': 'ca.sslserver.cert'}
+
+        DogtagInstance.update_cert_cs_cfg(
+            nickname, cert, directives,
+            dogtag.configured_constants().CS_CFG_PATH,
+            dogtag_constants)
 
 def replica_ca_install_check(config):
     if not config.setup_ca:
@@ -1697,11 +1596,6 @@ def install_replica_ca(config, postinstall=False):
     if ca.is_installed():
         sys.exit("A CA is already configured on this system.")
 
-    pkcs12_info = None
-    if ipautil.file_exists(config.dir + "/dogtagcert.p12"):
-        pkcs12_info = (config.dir + "/dogtagcert.p12",
-                       config.dir + "/dirsrv_pin.txt")
-
     ca = CAInstance(config.realm_name, certs.NSS_DIR,
             dogtag_constants=dogtag.install_constants)
     if postinstall:
@@ -1740,32 +1634,6 @@ def install_replica_ca(config, postinstall=False):
 
     return ca
 
-def update_cert_config(nickname, cert, dogtag_constants=None):
-    """
-    When renewing a CA subsystem certificate the configuration file
-    needs to get the new certificate as well.
-
-    nickname is one of the known nicknames.
-    cert is a DER-encoded certificate.
-    """
-
-    if dogtag_constants is None:
-        dogtag_constants = dogtag.configured_constants()
-
-    # The cert directive to update per nickname
-    directives = {'auditSigningCert cert-pki-ca': 'ca.audit_signing.cert',
-                  'ocspSigningCert cert-pki-ca': 'ca.ocsp_signing.cert',
-                  'caSigningCert cert-pki-ca': 'ca.signing.cert',
-                  'subsystemCert cert-pki-ca': 'ca.subsystem.cert',
-                  'Server-Cert cert-pki-ca': 'ca.sslserver.cert'}
-
-    with stopped_service(dogtag_constants.SERVICE_NAME,
-                         instance_name=dogtag_constants.PKI_INSTANCE_NAME):
-
-        installutils.set_directive(dogtag.configured_constants().CS_CFG_PATH,
-                                    directives[nickname],
-                                    base64.b64encode(cert),
-                                    quotes=False, separator='=')
 
 def update_people_entry(dercert):
     """
@@ -1802,11 +1670,11 @@ def update_people_entry(dercert):
             conn.connect(
                 bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password)
 
-            filter = conn.make_filter(
+            db_filter = conn.make_filter(
                 {'description': ';%s;%s' % (issuer, subject)},
                 exact=False, trailing_wildcard=False)
             try:
-                entries = conn.get_entries(base_dn, conn.SCOPE_SUBTREE, filter)
+                entries = conn.get_entries(base_dn, conn.SCOPE_SUBTREE, db_filter)
             except errors.NotFound:
                 entries = []
 
diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
new file mode 100644
index 0000000000000000000000000000000000000000..afe03e2b3c37069a7219982470828275428624e1
--- /dev/null
+++ b/ipaserver/install/dogtaginstance.py
@@ -0,0 +1,392 @@
+# Authors: Ade Lee <alee@redhat.com>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import base64
+import os
+import shutil
+import tempfile
+import traceback
+
+from pki.client import PKIConnection
+import pki.system
+
+from ipaplatform import services
+from ipapython import certmonger
+from ipapython import dogtag
+from ipapython import ipaldap
+from ipapython import ipautil
+from ipapython.dn import DN
+from ipaserver.install import service
+from ipaserver.install import installutils
+from ipaserver.install.installutils import stopped_service
+from ipapython.ipa_log_manager import *
+
+HTTPD_CONFD = "/etc/httpd/conf.d/"
+DEFAULT_DSPORT = dogtag.install_constants.DS_PORT
+
+PKI_USER = "pkiuser"
+PKI_DS_USER = dogtag.install_constants.DS_USER
+
+
+def check_inst(subsystem):
+    """
+    Validate that the appropriate dogtag/RHCS packages have been installed.
+    """
+
+    # Check for a couple of binaries we need
+    if not os.path.exists(dogtag.install_constants.SPAWN_BINARY):
+        return False
+    if not os.path.exists(dogtag.install_constants.DESTROY_BINARY):
+        return False
+
+    if not os.path.exists('/usr/share/pki/%s/conf/server.xml' % subsystem):
+        return False
+
+    return True
+
+
+def get_security_domain():
+    """
+    Get the security domain from the REST interface on the local Dogtag CA
+    This function will succeed if the local dogtag CA is up.
+    """
+    connection = PKIConnection()
+    domain_client = pki.system.SecurityDomainClient(connection)
+    info = domain_client.get_security_domain_info()
+    return info
+
+
+def is_installing_replica(sys_type):
+    """
+    We expect only one of each type of Dogtag subsystem in an IPA deployment.
+    That means that if a subsystem of the specified type has already been
+    deployed - and therefore appears in the security domain - then we must be
+    installing a replica.
+    """
+    info = get_security_domain()
+    try:
+        sys_list = info.systems[sys_type]
+        return len(sys_list.hosts) > 0
+    except KeyError:
+        return False
+
+
+class DogtagInstance(service.Service):
+    """
+    This is the base class for a Dogtag 10+ instance, which uses a
+    shared tomcat instance and DS to host the relevant subsystems.
+
+    It contains functions that will be common to installations of the
+    CA, KRA, and eventually TKS and TPS.
+    """
+
+    ADMIN_CERT_PATH = '/root/.dogtag/pki-tomcat/ca_admin.cert'
+    AGENT_P12_PATH = '/root/ca-agent.p12'
+
+    def __init__(self, realm, subsystem, service_desc, dogtag_constants=None):
+        if dogtag_constants is None:
+            dogtag_constants = dogtag.configured_constants()
+
+        service.Service.__init__(self,
+                                 '%sd' % dogtag_constants.PKI_INSTANCE_NAME,
+                                 service_desc=service_desc)
+
+        self.dogtag_constants = dogtag_constants
+        self.realm = realm
+        self.dm_password = None
+        self.admin_password = None
+        self.fqdn = None
+        self.domain = None
+        self.pkcs12_info = None
+        self.clone = False
+
+        self.basedn = DN(('o', 'ipa%s' % subsystem.lower()))
+        self.admin_user = DN(('uid', 'admin'), ('ou', 'people'), ('o', 'ipaca'))
+        self.agent_db = tempfile.mkdtemp(prefix="tmp-")
+        self.ds_port = DEFAULT_DSPORT
+        self.server_root = dogtag_constants.SERVER_ROOT
+        self.subsystem = subsystem
+        self.security_domain_name = "IPA"
+        self.tracking_nicknames = None
+
+        # replication parameters
+        self.master_host = None
+        self.master_replication_port = None
+        self.subject_base = None
+
+    def __del__(self):
+        shutil.rmtree(self.agent_db, ignore_errors=True)
+
+    def is_installed(self):
+        """
+        Determine if subsystem instance has been installed.
+
+        Returns True/False
+        """
+        return os.path.exists(os.path.join(
+            self.server_root, self.dogtag_constants.PKI_INSTANCE_NAME,
+            self.subsystem.lower()))
+
+    def spawn_instance(self, cfg_file, nolog_list=None):
+        """
+        Create and configure a new Dogtag instance using pkispawn.
+        Passes in a configuration file with IPA-specific
+        parameters.
+        """
+        subsystem = self.subsystem
+
+        # Define the things we don't want logged
+        if nolog_list is None:
+            nolog_list = []
+        nolog_list.extend([self.admin_password, self.dm_password])
+        nolog = tuple(nolog_list)
+
+        args = ["/usr/sbin/pkispawn",
+                "-s", subsystem,
+                "-f", cfg_file]
+
+        with open(cfg_file) as f:
+            root_logger.debug(
+                'Contents of pkispawn configuration file (%s):\n%s' %
+                (cfg_file, ipautil.nolog_replace(f.read(), nolog)))
+
+        try:
+            ipautil.run(args, nolog=nolog)
+        except ipautil.CalledProcessError, e:
+            root_logger.critical("failed to configure %s instance %s" %
+                                 (subsystem, e))
+            raise RuntimeError('Configuration of %s failed' % subsystem)
+
+    def enable(self):
+        self.backup_state("enabled", self.is_enabled())
+
+    # noinspection PyBroadException
+    def restart_instance(self):
+        try:
+            self.restart(self.dogtag_constants.PKI_INSTANCE_NAME)
+        except Exception:
+            root_logger.debug(traceback.format_exc())
+            root_logger.critical(
+                "Failed to restart the Dogtag instance."
+                "See the installation log for details.")
+
+    # noinspection PyBroadException
+    def start_instance(self):
+        try:
+            self.start(self.dogtag_constants.PKI_INSTANCE_NAME)
+        except Exception:
+            root_logger.debug(traceback.format_exc())
+            root_logger.critical(
+                "Failed to restart the Dogtag instance."
+                "See the installation log for details.")
+
+    # noinspection PyBroadException
+    def stop_instance(self):
+        try:
+            self.stop(self.dogtag_constants.PKI_INSTANCE_NAME)
+        except Exception:
+            root_logger.debug(traceback.format_exc())
+            root_logger.critical(
+                "Failed to restart the Dogtag instance."
+                "See the installation log for details.")
+
+    def enable_client_auth_to_db(self, config):
+        """
+        Enable client auth connection to the internal db.
+        Path to CS.cfg config file passed in.
+        """
+
+        with stopped_service(
+                self.dogtag_constants.SERVICE_NAME,
+                instance_name=self.dogtag_constants.PKI_INSTANCE_NAME):
+            installutils.set_directive(
+                config,
+                'authz.instance.DirAclAuthz.ldap.ldapauth.authtype',
+                'SslClientAuth', quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'authz.instance.DirAclAuthz.ldap.ldapauth.bindDN',
+                'uid=pkidbuser,ou=people,o=ipaca', quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'authz.instance.DirAclAuthz.ldap.ldapauth.clientCertNickname',
+                'subsystemCert cert-pki-ca', quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'authz.instance.DirAclAuthz.ldap.ldapconn.port',
+                str(dogtag.install_constants.DS_SECURE_PORT),
+                quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'authz.instance.DirAclAuthz.ldap.ldapconn.secureConn',
+                'true', quotes=False, separator='=')
+
+            installutils.set_directive(
+                config,
+                'internaldb.ldapauth.authtype',
+                'SslClientAuth', quotes=False, separator='=')
+
+            installutils.set_directive(
+                config,
+                'internaldb.ldapauth.bindDN',
+                'uid=pkidbuser,ou=people,o=ipaca', quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'internaldb.ldapauth.clientCertNickname',
+                'subsystemCert cert-pki-ca', quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'internaldb.ldapconn.port',
+                str(dogtag.install_constants.DS_SECURE_PORT),
+                quotes=False, separator='=')
+            installutils.set_directive(
+                config,
+                'internaldb.ldapconn.secureConn', 'true', quotes=False,
+                separator='=')
+
+    def uninstall(self):
+        if self.is_installed():
+            self.print_msg("Unconfiguring %s" % self.subsystem)
+
+        try:
+            ipautil.run(["/usr/sbin/pkidestroy", "-i",
+                         self.dogtag_constants.PKI_INSTANCE_NAME,
+                         "-s", self.subsystem])
+        except ipautil.CalledProcessError, e:
+            root_logger.critical("failed to uninstall %s instance %s"
+                                 % (self.subsystem, e))
+
+    def http_proxy(self):
+        """ Update the http proxy file  """
+        template_filename = ipautil.SHARE_DIR + "ipa-pki-proxy.conf"
+        sub_dict = dict(
+            DOGTAG_PORT=self.dogtag_constants.AJP_PORT,
+            CLONE='' if self.clone else '#',
+            FQDN=self.fqdn,
+        )
+        template = ipautil.template_file(template_filename, sub_dict)
+        with open(HTTPD_CONFD + "ipa-pki-proxy.conf", "w") as fd:
+            fd.write(template)
+
+    def __get_pin(self):
+        try:
+            return certmonger.get_pin('internal',
+                                      dogtag_constants=self.dogtag_constants)
+        except IOError, e:
+            root_logger.debug(
+                'Unable to determine PIN for the Dogtag instance: %s' % str(e))
+            raise RuntimeError(e)
+
+    def configure_renewal(self, nicknames=None):
+        """ Configure certmonger to renew system certs
+
+        @param nicknames: list of nicknames
+        """
+        cmonger = services.knownservices.certmonger
+        cmonger.enable()
+        services.knownservices.messagebus.start()
+        cmonger.start()
+
+        pin = self.__get_pin()
+
+        if nicknames is None:
+            nicknames = self.tracking_nicknames
+
+        for nickname in nicknames:
+            try:
+                certmonger.dogtag_start_tracking(
+                    ca='dogtag-ipa-ca-renew-agent',
+                    nickname=nickname,
+                    pin=pin,
+                    pinfile=None,
+                    secdir=self.dogtag_constants.ALIAS_DIR,
+                    pre_command='stop_pkicad',
+                    post_command='renew_ca_cert "%s"' % nickname)
+            except (ipautil.CalledProcessError, RuntimeError), e:
+                root_logger.error(
+                    "certmonger failed to start tracking certificate: %s" %
+                    str(e))
+
+    def stop_tracking_certificates(self, dogtag_constants, nicknames=None):
+        """Stop tracking our certificates. Called on uninstall.
+        """
+        cmonger = services.knownservices.certmonger
+        services.knownservices.messagebus.start()
+        cmonger.start()
+
+        if nicknames is None:
+            nicknames = self.tracking_nicknames
+
+        for nickname in nicknames:
+            try:
+                certmonger.stop_tracking(
+                    dogtag_constants.ALIAS_DIR, nickname=nickname)
+            except (ipautil.CalledProcessError, RuntimeError), e:
+                root_logger.error(
+                    "certmonger failed to stop tracking certificate: %s"
+                    % str(e))
+
+        cmonger.stop()
+
+    @staticmethod
+    def update_cert_cs_cfg(nickname, cert, directives, cs_cfg,
+                           dogtag_constants=None):
+        """
+        When renewing a Dogtag subsystem certificate the configuration file
+        needs to get the new certificate as well.
+
+        nickname is one of the known nicknames.
+        cert is a DER-encoded certificate.
+        directives is the list of directives to be updated for the subsystem
+        cs_cfg is the path to the CS.cfg file
+        """
+
+        if dogtag_constants is None:
+            dogtag_constants = dogtag.configured_constants()
+
+        with stopped_service(dogtag_constants.SERVICE_NAME,
+                             instance_name=dogtag_constants.PKI_INSTANCE_NAME):
+            installutils.set_directive(
+                cs_cfg,
+                directives[nickname],
+                base64.b64encode(cert),
+                quotes=False,
+                separator='=')
+
+    def get_admin_cert(self):
+        """
+        Get the certificate for the admin user by checking the ldap entry
+        for the user
+        """
+        root_logger.debug('Trying to find the certificate for the admin user')
+        conn = None
+
+        try:
+            conn = ipaldap.IPAdmin(self.fqdn, self.ds_port)
+            conn.do_simple_bind(
+                DN(('cn', 'Directory Manager')),
+                self.dm_password)
+
+            entry_attrs = conn.get_entry(self.admin_user, ['usercertificate'])
+            admin_cert = entry_attrs.get('usercertificate')[0]
+        finally:
+            if conn is not None:
+                conn.unbind()
+
+        return base64.b64encode(admin_cert)
diff --git a/ipaserver/install/drminstance.py b/ipaserver/install/drminstance.py
new file mode 100644
index 0000000000000000000000000000000000000000..3581a79429413f638c18afb87798e5772935fba3
--- /dev/null
+++ b/ipaserver/install/drminstance.py
@@ -0,0 +1,354 @@
+# Authors: Ade Lee <alee@redhat.com>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import ConfigParser
+import os
+import pwd
+import shutil
+import sys
+import tempfile
+
+from ipalib import api
+from ipaplatform import services
+from ipapython import dogtag
+from ipapython import ipaldap
+from ipapython import ipautil
+from ipapython.dn import DN
+from ipaserver.install import certs
+from ipaserver.install import cainstance
+from ipaserver.install import dsinstance
+from ipaserver.install import service
+from ipaserver.install.dogtaginstance import DogtagInstance
+from ipaserver.install.dogtaginstance import DEFAULT_DSPORT, PKI_USER
+from ipapython.ipa_log_manager import *
+
+# When IPA is installed with DNS support, this CNAME should hold all IPA
+# replicas with DRM configured
+IPA_DRM_RECORD = "ipa-drm"
+
+
+class DRMInstance(DogtagInstance):
+    """
+    We assume that the CA has already been installed, and we use the
+    same tomcat instance to host both the CA and DRM.
+    The mod_nss database will contain the RA agent cert that will be used
+    to do authenticated requests against dogtag.  The RA agent cert will
+    be the same for both the CA and DRM.
+    """
+
+    def __init__(self, realm, dogtag_constants=None):
+        """
+
+        :rtype : DRMInstance
+        """
+        if dogtag_constants is None:
+            dogtag_constants = dogtag.configured_constants()
+
+        DogtagInstance.__init__(self, realm, "KRA", "DRM server",
+                                dogtag_constants)
+        self.basedn = DN(('o', 'ipadrm'), ('o', 'ipaca'))
+        self.tracking_nicknames = ['auditSigningCert cert-pki-drm',
+                                   'transportCert cert-pki-drm',
+                                   'storageCert cert-pki-drm']
+
+    def configure_instance(self, host_name, domain, dm_password,
+                           admin_password, ds_port=DEFAULT_DSPORT,
+                           pkcs12_info=None, master_host=None,
+                           master_replication_port=None,
+                           subject_base=None):
+        """Create a DRM instance.
+
+           To create a clone, pass in pkcs12_info.
+        """
+        self.fqdn = host_name
+        self.domain = domain
+        self.dm_password = dm_password
+        self.admin_password = admin_password
+        self.ds_port = ds_port
+        self.pkcs12_info = pkcs12_info
+        if self.pkcs12_info is not None:
+            self.clone = True
+        self.master_host = master_host
+        self.master_replication_port = master_replication_port
+        if subject_base is None:
+            self.subject_base = DN(('O', self.realm))
+        else:
+            self.subject_base = subject_base
+
+        # Confirm that a DRM does not already exist
+        if self.is_installed():
+            raise RuntimeError(
+                "DRM already installed.")
+        # Confirm that a Dogtag 10 CA instance already exists
+        ca = cainstance.CAInstance(
+            api.env.realm, certs.NSS_DIR,
+            dogtag_constants=dogtag.Dogtag10Constants)
+        if not ca.is_installed():
+            raise RuntimeError(
+                "DRM configuration failed.  "
+                "A Dogtag CA must be installed first")
+
+        self.step("configuring DRM instance", self.__spawn_instance)
+        if not self.clone:
+            self.step("add RA user to DRM agent group",
+                      self.__add_ra_user_to_agent_group)
+        self.step("restarting DRM", self.restart_instance)
+        self.step("configure certificate renewals", self.configure_renewal)
+        self.step("Configure HTTP to proxy connections",
+                  self.http_proxy)
+
+        self.start_creation(runtime=210)
+
+    def __spawn_instance(self):
+        """
+        Create and configure a new DRM instance using pkispawn.
+        Creates a configuration file with IPA-specific
+        parameters and passes it to the base class to call pkispawn
+        """
+
+        # Create an empty and secured file
+        (cfg_fd, cfg_file) = tempfile.mkstemp()
+        os.close(cfg_fd)
+        pent = pwd.getpwnam(PKI_USER)
+        os.chown(cfg_file, pent.pw_uid, pent.pw_gid)
+
+        # Create KRA configuration
+        config = ConfigParser.ConfigParser()
+        config.optionxform = str
+        config.add_section("KRA")
+
+        # Security Domain Authentication
+        config.set("KRA", "pki_security_domain_https_port", "443")
+        config.set("KRA", "pki_security_domain_password", self.admin_password)
+        config.set("KRA", "pki_security_domain_user", "admin")
+
+        # issuing ca
+        config.set("KRA", "pki_issuing_ca_uri", "https://%s" %
+                   ipautil.format_netloc(self.fqdn, 443))
+
+        # Server
+        config.set("KRA", "pki_enable_proxy", "True")
+        config.set("KRA", "pki_restart_configured_instance", "False")
+        config.set("KRA", "pki_backup_keys", "True")
+        config.set("KRA", "pki_backup_password", self.admin_password)
+
+        # Client security database
+        config.set("KRA", "pki_client_database_dir", self.agent_db)
+        config.set("KRA", "pki_client_database_password", self.admin_password)
+        config.set("KRA", "pki_client_database_purge", "False")
+        config.set("KRA", "pki_client_pkcs12_password", self.admin_password)
+
+        # Administrator
+        config.set("KRA", "pki_admin_name", "admin")
+        config.set("KRA", "pki_admin_uid", "admin")
+        config.set("KRA", "pki_admin_email", "root@localhost")
+        config.set("KRA", "pki_admin_password", self.admin_password)
+        config.set("KRA", "pki_admin_nickname", "ipa-ca-agent")
+        config.set("KRA", "pki_admin_subject_dn",
+                   str(DN(('cn', 'ipa-ca-agent'), self.subject_base)))
+        config.set("KRA", "pki_import_admin_cert", "True")
+        config.set("KRA", "pki_admin_cert_file",
+                   DogtagInstance.ADMIN_CERT_PATH)
+        config.set("KRA", "pki_client_admin_cert_p12",
+                   DogtagInstance.AGENT_P12_PATH)
+
+        # Directory server
+        config.set("KRA", "pki_ds_ldap_port", str(self.ds_port))
+        config.set("KRA", "pki_ds_password", self.dm_password)
+        config.set("KRA", "pki_ds_base_dn", self.basedn)
+        config.set("KRA", "pki_ds_database", "ipaca")
+        config.set("KRA", "pki_ds_create_new_db", "False")
+
+        # Certificate subject DNs
+        config.set("KRA", "pki_subsystem_subject_dn",
+                   str(DN(('cn', 'CA Subsystem'), self.subject_base)))
+        config.set("KRA", "pki_ssl_server_subject_dn",
+                   str(DN(('cn', self.fqdn), self.subject_base)))
+        config.set("KRA", "pki_audit_signing_subject_dn",
+                   str(DN(('cn', 'DRM Audit'), self.subject_base)))
+        config.set(
+            "KRA", "pki_transport_subject_dn",
+            str(DN(('cn', 'DRM Transport Certificate'), self.subject_base)))
+        config.set(
+            "KRA", "pki_storage_subject_dn",
+            str(DN(('cn', 'DRM Storage Certificate'), self.subject_base)))
+
+        # Certificate nicknames
+        # Note that both the server certs and subsystem certs reuse
+        # the ca certs.
+        config.set("KRA", "pki_subsystem_nickname",
+                   "subsystemCert cert-pki-ca")
+        config.set("KRA", "pki_ssl_server_nickname",
+                   "Server-Cert cert-pki-ca")
+        config.set("KRA", "pki_audit_signing_nickname",
+                   "auditSigningCert cert-pki-drm")
+        config.set("KRA", "pki_transport_nickname",
+                   "transportCert cert-pki-drm")
+        config.set("KRA", "pki_storage_nickname",
+                   "storageCert cert-pki-drm")
+
+        # Shared db settings
+        # Needed because CA and KRA share the same database
+        # We will use the dbuser created for the CA
+        config.set("KRA", "pki_share_db", "True")
+        config.set(
+            "KRA", "pki_share_dbuser_dn",
+            str(DN(('uid', 'pkidbuser'), ('ou', 'people'), ('o', 'ipaca'))))
+
+        if self.clone:
+            drmfile = self.pkcs12_info[0]
+            shutil.copy(drmfile, "/tmp/drm.p12")
+            pent = pwd.getpwnam(PKI_USER)
+            os.chown("/tmp/drm.p12", pent.pw_uid, pent.pw_gid)
+
+            # create admin cert file if it does not exist
+            cert = DogtagInstance.get_admin_cert(self)
+            with open(DogtagInstance.ADMIN_CERT_PATH, "w") as admin_path:
+                admin_path.write(cert)
+
+            # Security domain registration
+            config.set("KRA", "pki_security_domain_hostname", self.master_host)
+            config.set("KRA", "pki_security_domain_https_port", "443")
+            config.set("KRA", "pki_security_domain_user", "admin")
+            config.set("KRA", "pki_security_domain_password",
+                       self.admin_password)
+
+            # Clone
+            config.set("KRA", "pki_clone", "True")
+            config.set("KRA", "pki_clone_pkcs12_path", "/tmp/drm.p12")
+            config.set("KRA", "pki_clone_pkcs12_password", self.dm_password)
+            config.set("KRA", "pki_clone_setup_replication", "False")
+            config.set(
+                "KRA", "pki_clone_uri",
+                "https://%s" % ipautil.format_netloc(self.master_host, 443))
+
+        # Generate configuration file
+        with open(cfg_file, "wb") as f:
+            config.write(f)
+
+        try:
+            DogtagInstance.spawn_instance(self, cfg_file)
+        finally:
+            os.remove(cfg_file)
+
+        shutil.move("/var/lib/pki/pki-tomcat/alias/kra_backup_keys.p12",
+                    "/root/kracert.p12")
+
+        root_logger.debug("completed creating DRM instance")
+
+    def __add_ra_user_to_agent_group(self):
+        """
+        Add RA agent created for CA to DRM agent group.
+        """
+        conn = ipaldap.IPAdmin(self.fqdn, self.ds_port)
+        conn.do_simple_bind(DN(('cn', 'Directory Manager')), self.dm_password)
+
+        entry_dn = DN(('uid', "ipara"), ('ou', 'People'), ('o', 'ipaca'))
+        dn = DN(('cn', 'Data Recovery Manager Agents'), ('ou', 'groups'),
+                self.basedn)
+        modlist = [(0, 'uniqueMember', '%s' % entry_dn)]
+        conn.modify_s(dn, modlist)
+
+        conn.unbind()
+
+    @staticmethod
+    def update_cert_config(nickname, cert, dogtag_constants=None):
+        """
+        When renewing a DRM subsystem certificate the configuration file
+        needs to get the new certificate as well.
+
+        nickname is one of the known nicknames.
+        cert is a DER-encoded certificate.
+        """
+
+        if dogtag_constants is None:
+            dogtag_constants = dogtag.configured_constants()
+
+        # The cert directive to update per nickname
+        directives = {
+            'auditSigningCert cert-pki-drm': 'kra.audit_signing.cert',
+            'storageCert cert-pki-drm': 'kra.storage.cert',
+            'transportCert cert-pki-drm': 'kra.transport.cert',
+            'subsystemCert cert-pki-drm': 'kra.subsystem.cert',
+            'Server-Cert cert-pki-ca': 'kra.sslserver.cert'}
+
+        DogtagInstance.update_cert_cs_cfg(
+            nickname, cert, directives,
+            dogtag.configured_constants().DRM_CS_CFG_PATH,
+            dogtag_constants)
+
+
+def install_replica_drm(config, postinstall=False):
+    """
+    Install a DRM on a replica.
+
+    There are two modes of doing this controlled:
+      - While the replica is being installed
+      - Post-replica installation
+
+    config is a ReplicaConfig object
+
+    Returns a DRM instance
+    """
+    # note that the cacert.p12 file is regenerated during the
+    # ipa-replica-prepare process and should include all the certs
+    # for the CA and DRM
+    drmfile = config.dir + "/cacert.p12"
+
+    if not ipautil.file_exists(drmfile):
+        raise RuntimeError(
+            "Unable to clone DRM."
+            "  cacert.p12 file not found in replica file")
+
+    _drm = DRMInstance(config.realm_name,
+                       dogtag_constants=dogtag.install_constants)
+    _drm.dm_password = config.dirman_password
+    _drm.subject_base = config.subject_base
+    if _drm.is_installed():
+        sys.exit("A DRM is already configured on this system.")
+
+    _drm.configure_instance(config.host_name, config.domain_name,
+                            config.dirman_password, config.dirman_password,
+                            pkcs12_info=(drmfile,),
+                            master_host=config.master_host_name,
+                            master_replication_port=config.ca_ds_port,
+                            subject_base=config.subject_base)
+
+    # Restart httpd since we changed it's config and added ipa-pki-proxy.conf
+    if postinstall:
+        services.knownservices.httpd.restart()
+
+    # The dogtag DS instance needs to be restarted after installation.
+    # The procedure for this is: stop dogtag, stop DS, start DS, start
+    # dogtag
+
+    service.print_msg("Restarting the directory and DRM servers")
+    _drm.stop(dogtag.install_constants.PKI_INSTANCE_NAME)
+    services.knownservices.dirsrv.restart()
+    _drm.start(dogtag.install_constants.PKI_INSTANCE_NAME)
+
+    return _drm
+
+if __name__ == "__main__":
+    standard_logging_setup("install.log")
+    ds = dsinstance.DsInstance()
+
+    drm = DRMInstance("EXAMPLE.COM")
+    drm.configure_instance("drmtest.example.com", "example.com",
+                           "password", "password")
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 2a9f3b618cc8e165821fefb9cede602cad8d6999..fe3734c7d8ad5b62c4105cce235339ab350e6353 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -38,6 +38,7 @@ import ldap
 from ipaserver.install import ldapupdate
 from ipaserver.install import replication
 from ipaserver.install import sysupgrade
+from ipalib import api
 from ipalib import errors
 from ipaplatform.tasks import tasks
 from ipalib.constants import CACERT
@@ -935,3 +936,85 @@ class DsInstance(service.Service):
             pass
 
         self.ldap_disconnect()
+
+    def find_subject_base(self):
+        """
+        Try to find the current value of certificate subject base.
+        1) Look in sysupgrade first
+        2) If no value is found there, look in DS (start DS if necessary)
+        3) Last resort, look in the certmap.conf itself
+        4) If all fails, log loudly and return None
+
+        Note that this method can only be executed AFTER the ipa server
+        is configured, the api is initialized elsewhere and
+        that a ticket already have been acquired.
+        """
+        root_logger.debug(
+            'Trying to find certificate subject base in sysupgrade')
+        subject_base = sysupgrade.get_upgrade_state(
+            'certmap.conf', 'subject_base')
+
+        if subject_base:
+            root_logger.debug(
+                'Found certificate subject base in sysupgrade: %s',
+                subject_base)
+            return subject_base
+
+        root_logger.debug(
+            'Unable to find certificate subject base in sysupgrade')
+        root_logger.debug(
+            'Trying to find certificate subject base in DS')
+
+        ds_is_running = is_ds_running()
+        if not ds_is_running:
+            try:
+                self.start()
+                ds_is_running = True
+            except ipautil.CalledProcessError as e:
+                root_logger.error('Cannot start DS to find certificate '
+                                  'subject base: %s', e)
+
+        if ds_is_running:
+            try:
+                api.Backend.ldap2.connect(autobind=True)
+                ret = api.Command['config_show']()
+                subject_base = str(
+                    ret['result']['ipacertificatesubjectbase'][0])
+                root_logger.debug(
+                    'Found certificate subject base in DS: %s', subject_base)
+            except errors.PublicError, e:
+                root_logger.error('Cannot connect to DS to find certificate '
+                                  'subject base: %s', e)
+            finally:
+                try:
+                    api.Backend.ldap2.disconnect()
+                except:
+                    pass
+
+        if not subject_base:
+            root_logger.debug('Unable to find certificate subject base in DS')
+            root_logger.debug('Trying to find certificate subject base in '
+                              'certmap.conf')
+
+            certmap_dir = config_dirname(
+                realm_to_serverid(api.env.realm)
+            )
+            try:
+                with open(os.path.join(certmap_dir, 'certmap.conf')) as f:
+                    for line in f:
+                        if line.startswith('certmap ipaca'):
+                            subject_base = line.strip().split(',')[-1]
+                            root_logger.debug(
+                                'Found certificate subject base in certmap.conf: '
+                                '%s', subject_base)
+
+            except IOError as e:
+                root_logger.error('Cannot open certmap.conf to find certificate '
+                                  'subject base: %s', e.strerror)
+
+        if subject_base:
+            return subject_base
+
+        root_logger.debug('Unable to find certificate subject base in '
+                          'certmap.conf')
+        return None
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 2c7479795bf35dd6f25eb5212b90f0e8bdb37430..0346e02127916fc6724c03aec6ebd4730e4a3a3f 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -34,13 +34,12 @@ from dns import resolver, rdatatype
 from dns.exception import DNSException
 import ldap
 
-from ipapython import ipautil, sysrestore, admintool, dogtag
+from ipapython import ipautil, sysrestore, admintool, dogtag, version
 from ipapython.admintool import ScriptError
 from ipapython.ipa_log_manager import *
 from ipalib.util import validate_hostname
 from ipapython import config
 from ipalib import errors
-from ipapython.dn import DN
 from ipaserver.install import certs, service
 from ipaplatform import services
 from ipaplatform.paths import paths
@@ -173,7 +172,7 @@ def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
             raise HostReverseLookupError("The host name %s does not match the reverse lookup %s" % (host_name, revname))
         verified.add(address)
 
-def record_in_hosts(ip, host_name=None, file=paths.HOSTS):
+def record_in_hosts(ip, host_name=None, conf_file=paths.HOSTS):
     """
     Search record in /etc/hosts - static table lookup for hostnames
 
@@ -183,9 +182,9 @@ def record_in_hosts(ip, host_name=None, file=paths.HOSTS):
 
     :param ip: IP address
     :param host_name: Optional hostname to search
-    :param file: Optional path to the lookup table
+    :param conf_file: Optional path to the lookup table
     """
-    hosts = open(file, 'r').readlines()
+    hosts = open(conf_file, 'r').readlines()
     for line in hosts:
         line = line.rstrip('\n')
         fields = line.partition('#')[0].split()
@@ -205,13 +204,13 @@ def record_in_hosts(ip, host_name=None, file=paths.HOSTS):
                     return None
             return (hosts_ip, names)
         except IndexError:
-            print "Warning: Erroneous line '%s' in %s" % (line, file)
+            print "Warning: Erroneous line '%s' in %s" % (line, conf_file)
             continue
 
     return None
 
-def add_record_to_hosts(ip, host_name, file=paths.HOSTS):
-    hosts_fd = open(file, 'r+')
+def add_record_to_hosts(ip, host_name, conf_file="/etc/hosts"):
+    hosts_fd = open(conf_file, 'r+')
     hosts_fd.seek(0, 2)
     hosts_fd.write(ip+'\t'+host_name+' '+host_name.split('.')[0]+'\n')
     hosts_fd.close()
@@ -511,20 +510,20 @@ def expand_replica_info(filename, password):
     """
     top_dir = tempfile.mkdtemp("ipa")
     tarfile = top_dir+"/files.tar"
-    dir = top_dir + "/realm_info"
+    dir_path = top_dir + "/realm_info"
     ipautil.decrypt_file(filename, tarfile, password, top_dir)
     ipautil.run(["tar", "xf", tarfile, "-C", top_dir])
     os.remove(tarfile)
 
-    return top_dir, dir
+    return top_dir, dir_path
 
-def read_replica_info(dir, rconfig):
+def read_replica_info(dir_path, rconfig):
     """
     Read the contents of a replica installation file.
 
     rconfig is a ReplicaConfig object
     """
-    filename = dir + "/realm_info"
+    filename = dir_path + "/realm_info"
     fd = open(filename)
     config = SafeConfigParser()
     config.readfp(fd)
@@ -555,6 +554,59 @@ def read_replica_info_dogtag_port(config_dir):
 
     return dogtag_master_ds_port
 
+def read_replica_info_drm_enabled(config_dir):
+    """
+    Check the replica info to determine if a DRM has been installed
+    on the master
+    """
+    default_file = config_dir + "/default.conf"
+    if not ipautil.file_exists(default_file):
+        return False
+    else:
+        with open(default_file) as fd:
+            config = SafeConfigParser()
+            config.readfp(fd)
+
+            enable_drm = bool(config.get("global", "enable_drm"))
+            return enable_drm
+
+
+def create_replica_config(dirman_password, filename, options):
+    try:
+        top_dir, dir = expand_replica_info(filename, dirman_password)
+        global REPLICA_INFO_TOP_DIR
+        REPLICA_INFO_TOP_DIR = top_dir
+    except Exception, e:
+        print "ERROR: Failed to decrypt or open the replica file."
+        print "Verify you entered the correct Directory Manager password."
+        sys.exit(1)
+    config = ReplicaConfig()
+    read_replica_info(dir, config)
+    root_logger.debug('Installing replica file with version %d (0 means no version in prepared file).' % config.version)
+    if config.version and config.version > version.NUM_VERSION:
+        root_logger.error('A replica file from a newer release (%d) cannot be installed on an older version (%d)' % (
+        config.version, version.NUM_VERSION))
+        sys.exit(1)
+    config.dirman_password = dirman_password
+    try:
+        host = get_host_name(options.no_host_dns)
+    except BadHostError, e:
+        root_logger.error(str(e))
+        sys.exit(1)
+    if config.host_name != host:
+        try:
+            print "This replica was created for '%s' but this machine is named '%s'" % (config.host_name, host)
+            if not ipautil.user_input("This may cause problems. Continue?", False):
+                sys.exit(0)
+            config.host_name = host
+            print ""
+        except KeyboardInterrupt:
+            sys.exit(0)
+    config.dir = dir
+    config.ca_ds_port = read_replica_info_dogtag_port(config.dir)
+    return config
+
+
 def check_server_configuration():
     """
     Check if IPA server is configured on the system.
@@ -571,6 +623,7 @@ def check_server_configuration():
     if not server_fstore.has_files():
         raise RuntimeError("IPA is not configured on this system.")
 
+
 def remove_file(filename):
     """
     Remove a file and log any exceptions raised.
@@ -581,6 +634,7 @@ def remove_file(filename):
     except Exception, e:
         root_logger.error('Error removing %s: %s' % (filename, str(e)))
 
+
 def rmtree(path):
     """
     Remove a directory structure and log any exceptions raised.
@@ -591,6 +645,7 @@ def rmtree(path):
     except Exception, e:
         root_logger.error('Error removing %s: %s' % (path, str(e)))
 
+
 def is_ipa_configured():
     """
     Using the state and index install files determine if IPA is already
@@ -763,7 +818,7 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
             raise ScriptError(
                 '%s server certificates found in %s, expecting only one' %
                 (len(server_certs), pkcs12_filename))
-        [(server_cert_name, server_cert_trust)] = server_certs
+        [(server_cert_name, _server_cert_trust)] = server_certs
 
         # Check we have the whole cert chain & the CA is in it
         trust_chain = nssdb.get_trust_chain(server_cert_name)
@@ -848,10 +903,11 @@ def stopped_service(service, instance_name=""):
             root_logger.debug('Starting %s%s.', service, log_instance_name)
             services.knownservices[service].start(instance_name)
 
+
 def check_entropy():
-    '''
+    """
     Checks if the system has enough entropy, if not, displays warning message
-    '''
+    """
     try:
         with open('/proc/sys/kernel/random/entropy_avail', 'r') as efname:
             if int(efname.read()) < 200:
@@ -860,8 +916,8 @@ def check_entropy():
                 service.print_msg(emsg)
                 root_logger.debug(emsg)
     except IOError as e:
-        root_logger.debug("Could not open /proc/sys/kernel/random/entropy_avail: %s" % \
-            e)
+        root_logger.debug(
+            "Could not open /proc/sys/kernel/random/entropy_avail: %s" % e)
     except ValueError as e:
-        root_logger.debug("Invalid value in /proc/sys/kernel/random/entropy_avail %s" % \
-            e)
+        root_logger.debug(
+            "Invalid value in /proc/sys/kernel/random/entropy_avail %s" % e)
diff --git a/ipaserver/install/ipa_replica_prepare.py b/ipaserver/install/ipa_replica_prepare.py
index 81b54211fb6d8b45c622d3d1bc336149ab89cb0e..7332e4584b2284bb130d33eed49bdd8c1571490b 100644
--- a/ipaserver/install/ipa_replica_prepare.py
+++ b/ipaserver/install/ipa_replica_prepare.py
@@ -371,6 +371,7 @@ class ReplicaPrepare(admintool.AdminTool):
         cacert_filename = paths.CACERT_PEM
         if ipautil.file_exists(cacert_filename):
             self.copy_info_file(cacert_filename, "cacert.pem")
+        self.copy_info_file("/etc/ipa/default.conf", "default.conf")
 
     def save_config(self):
         self.log.info("Finalizing configuration")
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 0b95ece79800258b5db17267f1cdb51dc5996113..d324b11d058a1ba9ad7413e1c421293bc1b5b717 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -1,10 +1,11 @@
 # Authors:
+#   Ade Lee <alee@redhat.com>
 #   Andrew Wnuk <awnuk@redhat.com>
 #   Jason Gerard DeRose <jderose@redhat.com>
 #   Rob Crittenden <rcritten@@redhat.com>
 #   John Dennis <jdennis@redhat.com>
 #
-# Copyright (C) 2009  Red Hat
+# Copyright (C) 2014  Red Hat
 # see file 'COPYING' for use and warranty information
 #
 # This program is free software; you can redistribute it and/or modify
@@ -34,7 +35,7 @@ variety of names, the open source version is called "dogtag".
 
 CMS consists of a number of servlets which in rough terms can be thought of as
 RPC commands. A servlet is invoked by making an HTTP request to a specific URL
-and passing URL arguments. Normally CMS responds with an HTTP reponse consisting
+and passing URL arguments. Normally CMS responds with an HTTP response consisting
 of HTML to be rendered by a web browser. This HTTP HTML response has both
 Javascript SCRIPT components and HTML rendering code. One of the Javascript
 SCRIPT blocks holds the data for the result. The rest of the response is derived
@@ -42,13 +43,13 @@ from templates associated with the servlet which may be customized. The
 templates pull the result data from Javascript variables.
 
 One way to get the result data is to parse the HTML looking for the Javascript
-varible initializations. Simple string searchs are not a robust method. First of
+variable initializations. Simple string searches are not a robust method. First of
 all one must be sure the string is only found in a Javascript SCRIPT block and
 not somewhere else in the HTML document. Some of the Javascript variable
 initializations are rather complex (e.g. lists of structures). It would be hard
 to correctly parse such complex and diverse Javascript. Existing Javascript
 parsers are not generally available. Finally, it's important to know the
-character encoding for strings. There is a somewhat complex set of precident
+character encoding for strings. There is a somewhat complex set of precedent
 rules for determining the current character encoding from the HTTP header,
 meta-equiv tags, mime Content-Type and charset attributes on HTML elements. All
 of this means trying to read the result data from a CMS HTML response is
@@ -119,7 +120,7 @@ values. Python also nicely handles type promotion transparently between int
 and long objects. For example if you multiply two int objects you may get back
 a long object if necessary. In general Python int and long objects may be
 freely mixed without the programmer needing to be aware of which type of
-intergral object is being operated on.
+integral object is being operated on.
 
 The leads to the following rule, always parse a string representing an
 integral value using the int() constructor even if it might have large
@@ -229,20 +230,31 @@ as a dict via the 'namespaces' keyword parameter of etree.XPath(). The predicate
 for the second location step uses the 're:' namespace to find the function name
 'match'. The re:match() takes a string to search as its first argument and a
 regular expression pattern as its second argument. In this example the string
-to seach is the node name of the location step because we called the built-in
+to search is the node name of the location step because we called the built-in
 node() function of XPath. The regular expression pattern we've passed says it's
 a match if the string begins with 'chapter' is followed by any number of
 digits and nothing else follows.
 
 '''
 
-from lxml import etree
-import urllib2
+import base64
 import datetime
+from lxml import etree
+import tempfile
 import time
+import urllib2
+
+import pki
+from pki.client import PKIConnection
+import pki.crypto as cryptoutil
+import pki.key as key
+from pki.kra import KRAClient
+
+from ipalib import Backend
 from ipapython.dn import DN
 import ipapython.dogtag
 from ipapython import ipautil
+from ipaserver.install.certs import CertDB
 
 # These are general status return values used when
 # CMSServlet.outputError() is invoked.
@@ -260,6 +272,7 @@ CMS_STATUS_REJECTED     = 5
 CMS_STATUS_ERROR        = 6
 CMS_STATUS_EXCEPTION    = 7
 
+
 def cms_request_status_to_string(request_status):
     '''
     :param request_status: The integral request status value
@@ -290,7 +303,7 @@ def parse_and_set_boolean_xml(node, response, response_name):
     '''
     :param node:          xml node object containing value to parse for boolean result
     :param response:      response dict to set boolean result in
-    :param response_name: name of the respone value to set
+    :param response_name: name of the response value to set
     :except ValueError:
 
     Read the value out of a xml text node and interpret it as a boolean value.
@@ -646,7 +659,7 @@ def parse_check_request_result_xml(doc):
     +-------------------------+---------------+-------------------+-----------------+
     |requestId                |string         |request_id         |string           |
     +-------------------------+---------------+-------------------+-----------------+
-    |staus                    |string         |cert_request_status|unicode [1]_     |
+    |status                    |string         |cert_request_status|unicode [1]_     |
     +-------------------------+---------------+-------------------+-----------------+
     |createdOn                |long, timestamp|created_on         |datetime.datetime|
     +-------------------------+---------------+-------------------+-----------------+
@@ -1199,6 +1212,59 @@ def parse_unrevoke_cert_xml(doc):
 
     return response
 
+
+# noinspection PyBroadException
+def host_has_service(host, ldap2, service='CA'):
+    """
+    :param host: A host which might be a master for a service.
+    :param ldap2: connection to the local database
+    :param service: The service for which the host might be a master.
+    :return:   (true, false)
+
+    Check if a specified host is a master for a specified service.
+    """
+    base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
+                 ('cn', 'etc'), api.env.basedn)
+    filter_attrs = {
+        'objectClass': 'ipaConfigObject',
+        'cn': service,
+        'ipaConfigString': 'enabledService',
+        }
+    query_filter = ldap2.make_filter(filter_attrs, rules='&')
+    try:
+        ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
+        if len(ent):
+            return True
+    except Exception:
+        pass
+    return False
+
+
+# noinspection PyBroadException
+def select_any_master(ldap2, service='CA'):
+    """
+    :param ldap2: connection to the local database
+    :param service: The service for which we're looking for a master.
+    :return:   host as str
+
+    Select any host which is a master for a specified service.
+    """
+    base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+                  api.env.basedn)
+    filter_attrs = {
+         'objectClass': 'ipaConfigObject',
+         'cn': service,
+         'ipaConfigString': 'enabledService',}
+    query_filter = ldap2.make_filter(filter_attrs, rules='&')
+    try:
+        ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
+        if len(ent):
+            entry = random.choice(ent)
+            return entry.dn[1].value
+    except Exception:
+        pass
+    return None
+
 #-------------------------------------------------------------------------------
 
 from ipalib import api, SkipPluginModule
@@ -1214,6 +1280,8 @@ from ipapython import dogtag
 from ipalib import _
 from ipaplatform.paths import paths
 
+
+# noinspection PyPep8Naming
 class ra(rabase.rabase):
     """
     Request Authority backend plugin.
@@ -1258,57 +1326,6 @@ class ra(rabase.rabase):
         self.error('%s.%s(): %s', self.fullname, func_name, err_msg)
         raise CertificateOperationError(error=err_msg)
 
-    def _host_has_service(self, host, service='CA'):
-        """
-        :param host: A host which might be a master for a service.
-        :param service: The service for which the host might be a master.
-        :return:   (true, false)
-
-        Check if a specified host is a master for a specified service.
-        """
-        ldap2 = self.api.Backend.ldap2
-        base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
-                     ('cn', 'etc'), api.env.basedn)
-        filter_attrs = {
-            'objectClass': 'ipaConfigObject',
-            'cn': service,
-            'ipaConfigString': 'enabledService',
-        }
-        filter = ldap2.make_filter(filter_attrs, rules='&')
-        try:
-            ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
-            if len(ent):
-                return True
-        except Exception, e:
-            pass
-        return False
-
-    def _select_any_master(self, service='CA'):
-        """
-        :param service: The service for which we're looking for a master.
-        :return:   host
-                   as str
-
-        Select any host which is a master for a specified service.
-        """
-        ldap2 = self.api.Backend.ldap2
-        base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
-                     api.env.basedn)
-        filter_attrs = {
-            'objectClass': 'ipaConfigObject',
-            'cn': service,
-            'ipaConfigString': 'enabledService',
-        }
-        filter = ldap2.make_filter(filter_attrs, rules='&')
-        try:
-            ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
-            if len(ent):
-                entry = random.choice(ent)
-                return entry.dn[1].value
-        except Exception, e:
-            pass
-        return None
-
     @cachedproperty
     def ca_host(self):
         """
@@ -1317,12 +1334,13 @@ class ra(rabase.rabase):
 
         Select our CA host.
         """
-        if self._host_has_service(host=api.env.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 self._host_has_service(host=api.env.host):
+            if host_has_service(api.env.host, ldap2, "CA"):
                 return api.env.host
-        host = self._select_any_master()
+        host = select_any_master(ldap2)
         if host:
             return host
         else:
@@ -1363,7 +1381,8 @@ class ra(rabase.rabase):
         parser = etree.XMLParser()
         doc = etree.fromstring(xml_text, parser)
         result = parse_func(doc)
-        self.debug("%s() xml_text:\n%s\nparse_result:\n%s" % (parse_func.__name__, xml_text, result))
+        self.debug("%s() xml_text:\n%s\n"
+                   "parse_result:\n%s" % (parse_func.__name__, xml_text, result))
         return result
 
     def check_request_status(self, request_id):
@@ -1410,7 +1429,7 @@ class ra(rabase.rabase):
                           xml='true')
 
         # Parse and handle errors
-        if (http_status != 200):
+        if http_status != 200:
             self.raise_certificate_operation_error('check_request_status',
                                                    detail=http_reason_phrase)
 
@@ -1440,10 +1459,10 @@ class ra(rabase.rabase):
         Retrieve an existing certificate.
 
         :param serial_number: Certificate serial number. Must be a string value
-                              because serial numbers may be of any magnitue and
+                              because serial numbers may be of any magnitude and
                               XMLRPC cannot handle integers larger than 64-bit.
                               The string value should be decimal, but may optionally
-                              be prefixed with a hex radix prefix if the integal value
+                              be prefixed with a hex radix prefix if the integral value
                               is represented as hexadecimal. If no radix prefix is
                               supplied the string will be interpreted as decimal.
 
@@ -1496,7 +1515,7 @@ class ra(rabase.rabase):
 
 
         # Parse and handle errors
-        if (http_status != 200):
+        if http_status != 200:
             self.raise_certificate_operation_error('get_certificate',
                                                    detail=http_reason_phrase)
 
@@ -1563,7 +1582,7 @@ class ra(rabase.rabase):
                          cert_request=csr,
                          xml='true')
         # Parse and handle errors
-        if (http_status != 200):
+        if http_status != 200:
             self.raise_certificate_operation_error('request_certificate',
                                                    detail=http_reason_phrase)
 
@@ -1604,10 +1623,10 @@ class ra(rabase.rabase):
     def revoke_certificate(self, serial_number, revocation_reason=0):
         """
         :param serial_number: Certificate serial number. Must be a string value
-                              because serial numbers may be of any magnitue and
+                              because serial numbers may be of any magnitude and
                               XMLRPC cannot handle integers larger than 64-bit.
                               The string value should be decimal, but may optionally
-                              be prefixed with a hex radix prefix if the integal value
+                              be prefixed with a hex radix prefix if the integral value
                               is represented as hexadecimal. If no radix prefix is
                               supplied the string will be interpreted as decimal.
         :param revocation_reason: Integer code of revocation reason.
@@ -1644,7 +1663,7 @@ class ra(rabase.rabase):
                          xml='true')
 
         # Parse and handle errors
-        if (http_status != 200):
+        if http_status != 200:
             self.raise_certificate_operation_error('revoke_certificate',
                                                    detail=http_reason_phrase)
 
@@ -1668,10 +1687,10 @@ class ra(rabase.rabase):
     def take_certificate_off_hold(self, serial_number):
         """
         :param serial_number: Certificate serial number. Must be a string value
-                              because serial numbers may be of any magnitue and
+                              because serial numbers may be of any magnitude and
                               XMLRPC cannot handle integers larger than 64-bit.
                               The string value should be decimal, but may optionally
-                              be prefixed with a hex radix prefix if the integal value
+                              be prefixed with a hex radix prefix if the integral value
                               is represented as hexadecimal. If no radix prefix is
                               supplied the string will be interpreted as decimal.
 
@@ -1704,7 +1723,7 @@ class ra(rabase.rabase):
                          xml='true')
 
         # Parse and handle errors
-        if (http_status != 200):
+        if http_status != 200:
             self.raise_certificate_operation_error('take_certificate_off_hold',
                                                    detail=http_reason_phrase)
 
@@ -1866,4 +1885,163 @@ class ra(rabase.rabase):
 
         return results
 
+
 api.register(ra)
+
+
+# ----------------------------------------------------------------------------
+# noinspection PyPep8Naming
+class drm(Backend):
+    """
+    DRM backend plugin (for Vault)
+    """
+
+    def __init__(self):
+        if api.env.in_tree:
+            self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
+            pwd_file = self.sec_dir + os.sep + '.pwd'
+            self.pem_file = self.sec_dir + os.sep + ".pemfile"
+        else:
+            self.sec_dir = "/etc/httpd/alias"
+            pwd_file = "/etc/httpd/alias/pwdfile.txt"
+            self.pem_file = "/etc/httpd/alias/agent.pem"
+
+        self.transport_nick = "IPA DRM Transport Cert"
+        self.password = ""
+        with open(pwd_file, "r") as f:
+            self.password = f.readline().strip()
+
+        self.keyclient = None
+        super(drm, self).__init__()
+
+    def _create_pem_file(self):
+        (p12_pwd_fd, p12_pwd_fname) = tempfile.mkstemp()
+        (p12_fd, p12_fname) = tempfile.mkstemp()
+
+        try:
+            os.write(p12_pwd_fd, self.password)
+            os.close(p12_pwd_fd)
+            os.close(p12_fd)
+
+            certdb = CertDB(api.env.realm)
+            certdb.export_pkcs12(p12_fname, p12_pwd_fname, "ipaCert")
+
+            certdb.install_pem_from_p12(p12_fname, self.password, self.pem_file)
+        except:
+            self.debug("Error when creating PEM file for DRM operations")
+            raise
+        finally:
+            os.remove(p12_fname)
+            os.remove(p12_pwd_fname)
+
+    def _transport_cert_present(self):
+        # certutil -L -d db_dir -n cert_nick
+        certdb = CertDB(api.env.realm)
+        return certdb.has_nickname(self.transport_nick)
+
+    def _setup(self):
+        """
+        Do initial setup and crypt initialization of the DRM client
+        """
+        #set up pem file if not present
+        if not os.path.exists(self.pem_file):
+            self._create_pem_file()
+
+        # set up connection
+        connection = PKIConnection('https', self.drm_host, 443, 'kra')
+        connection.set_authentication_cert(self.pem_file)
+
+        crypto = cryptoutil.NSSCryptoProvider(self.sec_dir, self.password)
+
+        #create kraclient
+        kraclient = KRAClient(connection, crypto)
+
+        # get transport cert if needed
+        if not self._transport_cert_present():
+            transport_cert = kraclient.system_certs.get_transport_cert()
+            tcert = transport_cert[
+                len(pki.CERT_HEADER):
+                len(transport_cert) - len(pki.CERT_FOOTER)]
+
+            crypto.import_cert(
+                self.transport_nick,
+                base64.decodestring(tcert), "u,u,u")
+
+        crypto.initialize()
+
+        self.keyclient = kraclient.keys
+        self.keyclient.set_transport_cert(self.transport_nick)
+
+    @cachedproperty
+    def drm_host(self):
+        """
+        :return:   host
+                   as str
+
+        Select our DRM host.
+        """
+        ldap2 = self.api.Backend.ldap2
+        if host_has_service(api.env.drm_host, ldap2, "drm"):
+            return api.env.drm_host
+        if api.env.host != api.env.drm_host:
+            if host_has_service(api.env.host, ldap2, "drm"):
+                return api.env.host
+        host = select_any_master(ldap2, "drm")
+        if host:
+            return host
+        else:
+            return api.env.drm_host
+
+    def store_secret(self, secret, client_key_id):
+        """
+        Store a secret in the DRM
+        """
+        if self.keyclient is None:
+            self._setup()
+        data_type = key.KeyClient.PASS_PHRASE_TYPE
+        response = self.keyclient.archive_key(client_key_id,
+                                              data_type,
+                                              secret)
+
+        return response.get_key_id(), None
+
+    def retrieve_secret(self, key_id):
+        """
+        Retrieve a secret from the DRM
+
+        This function will call retrieve_key() without a wrapping key.
+        This relies on the DRM client to generate a wrapping key 
+        (and wrap it with the DRM transport cert), and is completely
+        transparent to the IPA server.  What is returned to the caller
+        is the unencrypted secret.  This is OK in the vault case because
+        the secret is pre-encrypted by the client
+        """
+        if self.keyclient is None:
+            self._setup()
+        secret = self.keyclient.retrieve_key(key_id)
+        return secret.data
+
+    def generate_symmetric_key(self, algorithm, bit_length, client_key_id):
+        """
+        Generate a symmetric key
+
+        This calls generate_symmetric_key() on the DRM passing in the
+        algorithm, bit_length and id (used as the client_key_id) from
+        the secret.  The remaining parameters are not used.
+
+        Returns a keyId which will be stored in an EncryptedDatum
+        table for later retrieval.
+        """
+        if self.keyclient is None:
+            self._setup()
+        usages = [key.SymKeyGenerationRequest.DECRYPT_USAGE,
+                  key.SymKeyGenerationRequest.ENCRYPT_USAGE]
+
+        response = self.keyclient.generate_symmetric_key(
+            client_key_id,
+            algorithm,
+            bit_length,
+            usages)
+        return response.get_key_id(), None
+
+api.register(drm)
-- 
1.9.3

