>From 801fac5b642adf8077305ce74ad58e83cb271511 Mon Sep 17 00:00:00 2001
From: Ade Lee <alee@redhat.com>
Date: Fri, 17 Jul 2015 16:20:46 -0400
Subject: [PATCH] Add options to allow GSSAPI on replication agreements

Ticket 1414
---
 .../certsrv/system/ConfigurationRequest.java       |  26 ++++-
 .../cms/servlet/csadmin/ConfigurationUtils.java    | 127 ++++++++++++---------
 .../dogtagpki/server/rest/SystemConfigService.java |  53 +++++----
 base/server/etc/default.cfg                        |   4 +-
 base/server/man/man5/pki_default.cfg.5             |  19 ++-
 base/server/man/man8/pkispawn.8                    |  11 ++
 .../python/pki/server/deployment/pkihelper.py      |   7 ++
 7 files changed, 170 insertions(+), 77 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java b/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
index 0682ac98f151f5405764636e77971974a91eed8c..ad972d88ab69afd1d9a6baa0cb20cc55fcfc80b2 100644
--- a/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
+++ b/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
@@ -127,6 +127,12 @@ public class ConfigurationRequest {
     protected String replicationPassword;
 
     @XmlElement
+    protected String replicationBindGroup;
+
+    @XmlElement
+    protected String replicationBindMethod;
+
+    @XmlElement
     protected String setupReplication;
 
     @XmlElement
@@ -513,6 +519,22 @@ public class ConfigurationRequest {
         this.replicationPassword = replicationPassword;
     }
 
+    public String getReplicationBindGroup() {
+        return replicationBindGroup;
+    }
+
+    public void setReplicationBindGroup(String replicationBindGroup) {
+        this.replicationBindGroup = replicationBindGroup;
+    }
+
+    public String getReplicationBindMethod() {
+        return replicationBindMethod;
+    }
+
+    public void setReplicationBindMethod(String replicationBindMethod) {
+        this.replicationBindMethod = replicationBindMethod;
+    }
+
     public boolean getSetupReplication() {
         // default to true
         if (setupReplication == null) {
@@ -946,7 +968,9 @@ public class ConfigurationRequest {
                ", sharedDBUserDN=" + sharedDBUserDN +
                ", createNewDB=" + createNewDB +
                ", setupReplication=" + setupReplication +
-               ", subordinateSecurityDomainName" + subordinateSecurityDomainName +
+               ", subordinateSecurityDomainName=" + subordinateSecurityDomainName +
+               ", replicationBindMethod=" + replicationBindMethod +
+               ", replicationBindGroup=" + replicationBindGroup +
                "]";
     }
 
diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
index c8ab38ce71643ae49e5c14746e7a815cea5b263a..b48d121b94151e5a8400d21327dec506bb6bd02f 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
@@ -1878,7 +1878,9 @@ public class ConfigurationUtils {
         String machinename = cs.getString("machineName", "");
         String instanceId = cs.getString("instanceId", "");
         String secure = cs.getString("internaldb.ldapconn.secureConn");
-        String replicationSecurity = cs.getString("internaldb.ldapconn.replicationSecurity");
+        String replicationSecurity = cs.getString("internaldb.ldapconn.replicationSecurity", "LDAP");
+        String replicationBindGroup = cs.getString("preop.database.replication.bindgroup", null);
+        String replicationBindMethod = cs.getString("preop.database.replication.bindmethod", "Simple");
         int masterReplicationPort = cs.getInteger("internaldb.ldapconn.masterReplicationPort");
         int cloneReplicationPort = cs.getInteger("internaldb.ldapconn.cloneReplicationPort");
 
@@ -1891,35 +1893,12 @@ public class ConfigurationUtils {
         cs.commit(false);
 
         // get connection to master
-        LDAPConnection masterConn = null;
-        ILdapConnFactory masterFactory = null;
-        try {
-            IConfigStore masterCfg = cs.getSubStore("preop.internaldb.master");
-            masterFactory = CMS.getLdapBoundConnFactory("ConfigurationUtils");
-            masterFactory.init(masterCfg);
-            masterConn = masterFactory.getConn();
-        } catch (Exception e) {
-            CMS.debug("setupEeplication: Failed to set up connection to master:" + e.toString());
-            e.printStackTrace();
-            releaseConnection(masterConn);
-            throw new IOException("Failed to set up replication: No connection to master", e);
-        }
+        IConfigStore masterCfg = cs.getSubStore("preop.internaldb.master");
+        LDAPConnection masterConn = getDBConnection(masterCfg, "master");
 
         // get connection to replica
-        LDAPConnection replicaConn = null;
-        ILdapConnFactory replicaFactory = null;
-        try {
-            IConfigStore replicaCfg = cs.getSubStore("internaldb");
-            replicaFactory = CMS.getLdapBoundConnFactory("ConfigurationUtils");
-            replicaFactory.init(replicaCfg);
-            replicaConn = replicaFactory.getConn();
-        } catch (Exception e) {
-            CMS.debug("SetupReplication: Failed to set up connection to replica:" + e.toString());
-            e.printStackTrace();
-            releaseConnection(masterConn);
-            releaseConnection(replicaConn);
-            throw new IOException("Failed to set up replication: No connection to replica", e);
-        }
+        IConfigStore replicaCfg = cs.getSubStore("internaldb");
+        LDAPConnection replicaConn = getDBConnection(replicaCfg, "replica");
 
         try {
             String master_hostname = cs.getString("preop.internaldb.master.ldapconn.host", "");
@@ -1935,8 +1914,10 @@ public class ConfigurationUtils {
             String masterBindUser = "Replication Manager " + masterAgreementName;
             String cloneBindUser = "Replication Manager " + cloneAgreementName;
 
-            createReplicationManager(masterConn, masterBindUser, master_replicationpwd);
-            createReplicationManager(replicaConn, cloneBindUser, replica_replicationpwd);
+            if (replicationBindGroup == null) {
+                createReplicationManager(masterConn, masterBindUser, master_replicationpwd);
+                createReplicationManager(replicaConn, cloneBindUser, replica_replicationpwd);
+            }
 
             String dir1 = getInstanceDir(masterConn);
             createChangeLog(masterConn, dir1 + "/changelogs");
@@ -1946,19 +1927,23 @@ public class ConfigurationUtils {
 
             int replicaId = cs.getInteger("dbs.beginReplicaNumber", 1);
 
-            replicaId = enableReplication(replicadn, masterConn, masterBindUser, basedn, replicaId);
-            replicaId = enableReplication(replicadn, replicaConn, cloneBindUser, basedn, replicaId);
+            replicaId = enableReplication(replicadn, masterConn, masterBindUser, basedn, replicaId,
+                    replicationBindGroup);
+            replicaId = enableReplication(replicadn, replicaConn, cloneBindUser, basedn, replicaId,
+                    replicationBindGroup);
             cs.putString("dbs.beginReplicaNumber", Integer.toString(replicaId));
 
             CMS.debug("setupReplication: Finished enabling replication");
 
             createReplicationAgreement(replicadn, masterConn, masterAgreementName,
                     replica_hostname, cloneReplicationPort, replica_replicationpwd, basedn,
-                    cloneBindUser, secure, replicationSecurity);
+                    cloneBindUser, secure, replicationSecurity, replicationBindGroup,
+                    replicationBindMethod);
 
             createReplicationAgreement(replicadn, replicaConn, cloneAgreementName,
                     master_hostname, masterReplicationPort, master_replicationpwd, basedn,
-                    masterBindUser, secure, replicationSecurity);
+                    masterBindUser, secure, replicationSecurity, replicationBindGroup,
+                    replicationBindMethod);
 
             // initialize consumer
             initializeConsumer(replicadn, masterConn, masterAgreementName);
@@ -1994,6 +1979,22 @@ public class ConfigurationUtils {
         }
 }
 
+    private static LDAPConnection getDBConnection(IConfigStore cs, String tag) throws IOException {
+        LDAPConnection conn = null;
+        ILdapConnFactory factory = null;
+        try {
+            factory = CMS.getLdapBoundConnFactory("ConfigurationUtils");
+            factory.init(cs);
+            conn = factory.getConn();
+        } catch (Exception e) {
+            CMS.debug("setupReplication: Failed to set up connection to " + tag + ":" + e.toString());
+            e.printStackTrace();
+            releaseConnection(conn);
+            throw new IOException("Failed to set up replication: No connection to " + tag, e);
+        }
+        return conn;
+    }
+
     public static void createReplicationManager(LDAPConnection conn, String bindUser, String pwd)
             throws LDAPException {
         LDAPAttributeSet attrs = null;
@@ -2075,11 +2076,12 @@ public class ConfigurationUtils {
         CMS.debug("createChangeLog: Successfully create change log entry");
     }
 
-    public static int enableReplication(String replicadn, LDAPConnection conn, String bindUser, String basedn, int id)
-            throws LDAPException {
+    public static int enableReplication(String replicadn, LDAPConnection conn, String bindUser, String basedn, int id,
+            String bindGroup) throws LDAPException {
         CMS.debug("enableReplication: replicadn: " + replicadn);
         LDAPAttributeSet attrs = null;
         LDAPEntry entry = null;
+        String bindDN = "cn=" + LDAPUtil.escapeRDNValue(bindUser) + ",ou=csusers,cn=config";
         try {
             attrs = new LDAPAttributeSet();
             attrs.add(new LDAPAttribute("objectclass", "top"));
@@ -2087,8 +2089,11 @@ public class ConfigurationUtils {
             attrs.add(new LDAPAttribute("objectclass", "extensibleobject"));
             attrs.add(new LDAPAttribute("nsDS5ReplicaRoot", basedn));
             attrs.add(new LDAPAttribute("nsDS5ReplicaType", "3"));
-            attrs.add(new LDAPAttribute("nsDS5ReplicaBindDN",
-                    "cn=" + LDAPUtil.escapeRDNValue(bindUser) + ",ou=csusers,cn=config"));
+            if (bindGroup == null) {
+                attrs.add(new LDAPAttribute("nsDS5ReplicaBindDN", bindDN));
+            } else {
+                attrs.add(new LDAPAttribute("nsds5replicabinddngroup", bindGroup));
+            }
             attrs.add(new LDAPAttribute("cn", "replica"));
             attrs.add(new LDAPAttribute("nsDS5ReplicaId", Integer.toString(id)));
             attrs.add(new LDAPAttribute("nsds5flags", "1"));
@@ -2101,11 +2106,11 @@ public class ConfigurationUtils {
                 CMS.debug("enableReplication: " + replicadn + " has already been used");
 
                 try {
-                    entry = conn.read(replicadn);
-                    LDAPAttribute attr = entry.getAttribute("nsDS5ReplicaBindDN");
-                    attr.addValue("cn=" + LDAPUtil.escapeRDNValue(bindUser) + ",ou=csusers,cn=config");
-                    LDAPModification mod = new LDAPModification(LDAPModification.REPLACE, attr);
-                    conn.modify(replicadn, mod);
+                    if (bindGroup == null) {
+                        addOrReplaceAttribute(conn, replicadn, "nsDS5ReplicaBindDN", bindDN);
+                    } else {
+                        addOrReplaceAttribute(conn, replicadn, "nsDS5ReplicaBindGroup", bindGroup);
+                    }
                 } catch (LDAPException ee) {
                     CMS.debug("enableReplication: Failed to modify "
                             + replicadn + " entry. Exception: " + e.toString());
@@ -2122,9 +2127,26 @@ public class ConfigurationUtils {
         return id + 1;
     }
 
+    private static void addOrReplaceAttribute(LDAPConnection conn, String entryDN, String attrName, String attrValue)
+            throws LDAPException {
+        LDAPModification mod = null;
+        LDAPAttribute attr = null;
+
+        LDAPEntry entry = conn.read(entryDN);
+        attr = entry.getAttribute(attrName);
+        if (attr != null) {
+            attr.addValue(attrValue);
+            mod = new LDAPModification(LDAPModification.REPLACE, attr);
+        } else {
+            attr = new LDAPAttribute(attrName, attrValue);
+            mod = new LDAPModification(LDAPModification.ADD, attr);
+        }
+        conn.modify(entryDN, mod);
+    }
+
     public static void createReplicationAgreement(String replicadn, LDAPConnection conn, String name,
             String replicahost, int replicaport, String replicapwd, String basedn, String bindUser,
-            String secure, String replicationSecurity) throws LDAPException {
+            String secure, String replicationSecurity, String bindGroup, String bindMethod) throws LDAPException {
         String dn = "cn=" + LDAPUtil.escapeRDNValue(name) + "," + replicadn;
         CMS.debug("createReplicationAgreement: dn: " + dn);
         LDAPEntry entry = null;
@@ -2139,16 +2161,15 @@ public class ConfigurationUtils {
             attrs.add(new LDAPAttribute("nsDS5ReplicaHost", replicahost));
 
             attrs.add(new LDAPAttribute("nsDS5ReplicaPort", "" + replicaport));
-            attrs.add(new LDAPAttribute("nsDS5ReplicaBindDN",
-                    "cn=" + LDAPUtil.escapeRDNValue(bindUser) + ",ou=csusers,cn=config"));
-            attrs.add(new LDAPAttribute("nsDS5ReplicaBindMethod", "Simple"));
-            attrs.add(new LDAPAttribute("nsds5replicacredentials", replicapwd));
-
-            if (replicationSecurity.equals("SSL")) {
-                attrs.add(new LDAPAttribute("nsDS5ReplicaTransportInfo", "SSL"));
-            } else if (replicationSecurity.equals("TLS")) {
-                attrs.add(new LDAPAttribute("nsDS5ReplicaTransportInfo", "TLS"));
+            if (bindGroup != null) {
+                attrs.add(new LDAPAttribute("nsDS5ReplicaBindDN",
+                        "cn=" + LDAPUtil.escapeRDNValue(bindUser) + ",ou=csusers,cn=config"));
+                attrs.add(new LDAPAttribute("nsds5replicacredentials", replicapwd));
+            } else {
+                attrs.add(new LDAPAttribute("nsDS5ReplicaBindMethod", bindGroup));
             }
+            attrs.add(new LDAPAttribute("nsDS5ReplicaBindMethod", bindMethod));
+            attrs.add(new LDAPAttribute("nsDS5ReplicaTransportInfo", replicationSecurity));
 
             CMS.debug("About to set description attr to " + name);
             attrs.add(new LDAPAttribute("description", name));
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
index e7a99601b473aca6a466873d4a776044d5e16e22..9e9e370ce57a7371a0e9071d5b8a1f9714d4de18 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
@@ -50,6 +50,7 @@ import org.mozilla.jss.util.IncorrectPasswordException;
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.EPropertyNotFound;
 import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.ca.ICertificateAuthority;
@@ -649,6 +650,12 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
         cs.putString("preop.database.removeData", data.getRemoveData());
         cs.putBoolean("preop.database.createNewDB", data.getCreateNewDB());
         cs.putBoolean("preop.database.setupReplication", data.getSetupReplication());
+        if (data.getReplicationBindMethod() != null) {
+            cs.putString("preop.database.replication.bindmethod", data.getReplicationBindMethod());
+        }
+        if (data.getReplicationBindGroup() != null) {
+            cs.putString("preop.database.replication.bindgroup", data.getReplicationBindMethod());
+        }
     }
 
     public void initializeDatabase(ConfigurationRequest data) {
@@ -691,7 +698,7 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
             if ((cloneReplicationPort == data.getDsPort()) && (data.getSecureConn().equals("true"))) {
                 replicationSecurity = "SSL";
             } else if (replicationSecurity == null) {
-                replicationSecurity = "None";
+                replicationSecurity = "LDAP";
             }
             cs.putString("internaldb.ldapconn.replicationSecurity", replicationSecurity);
 
@@ -699,29 +706,10 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
         }
 
         try {
-            /* BZ 430745 create password for replication manager */
-            // use user-provided password if specified
-            String replicationPassword = data.getReplicationPassword();
-
-            if (StringUtils.isEmpty(replicationPassword)) {
-                // generate random password
-                replicationPassword = Integer.toString(new Random().nextInt());
-            }
-
-            IConfigStore psStore = null;
-            String passwordFile = null;
-            passwordFile = cs.getString("passwordFile");
-            psStore = CMS.createFileConfigStore(passwordFile);
-            psStore.putString("internaldb", data.getBindpwd());
-            if (data.getSetupReplication()) {
-                psStore.putString("replicationdb", replicationPassword);
-            }
-            psStore.commit(false);
-
             if (!data.getStepTwo()) {
                 ConfigurationUtils.populateDB();
 
-                cs.putString("preop.internaldb.replicationpwd", replicationPassword);
+                storeReplicationPassword(data);
                 cs.putString("preop.database.removeData", "false");
                 if (data.getSharedDB()) {
                     cs.putString("preop.internaldb.dbuser", data.getSharedDBUserDN());
@@ -743,6 +731,29 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
         }
     }
 
+    private void storeReplicationPassword(ConfigurationRequest data) throws EPropertyNotFound, EBaseException {
+        /* BZ 430745 create password for replication manager */
+        // use user-provided password if specified
+        String replicationPassword = data.getReplicationPassword();
+
+        if (StringUtils.isEmpty(replicationPassword)) {
+            // generate random password
+            replicationPassword = Integer.toString(new Random().nextInt());
+        }
+
+        IConfigStore psStore = null;
+        String passwordFile = null;
+        passwordFile = cs.getString("passwordFile");
+        psStore = CMS.createFileConfigStore(passwordFile);
+        psStore.putString("internaldb", data.getBindpwd());
+        if (data.getSetupReplication()) {
+            psStore.putString("replicationdb", replicationPassword);
+        }
+        psStore.commit(false);
+
+        cs.putString("preop.internaldb.replicationpwd", replicationPassword);
+    }
+
     public void configureHierarchy(ConfigurationRequest data) {
         if (csType.equals("CA") && !data.isClone()) {
             if (data.getHierarchy().equals("root")) {
diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg
index 58f338692ac4f1c2637b575e27db2bec6254905b..12deef21356c585b4a0be46f98b8aeea4d80905b 100644
--- a/base/server/etc/default.cfg
+++ b/base/server/etc/default.cfg
@@ -191,7 +191,9 @@ pki_clone_pkcs12_path=
 pki_clone_replicate_schema=True
 pki_clone_replication_master_port=
 pki_clone_replication_clone_port=
-pki_clone_replication_security=None
+pki_clone_replication_security=LDAP
+pki_clone_replication_bind_group=
+pki_clone_replication_bind_method=Simple
 pki_clone_setup_replication=True
 pki_master_hostname=%(pki_security_domain_hostname)s
 pki_master_https_port=%(pki_security_domain_https_port)s
diff --git a/base/server/man/man5/pki_default.cfg.5 b/base/server/man/man5/pki_default.cfg.5
index 6623ce6fd5d5cf425bd93e73eb450d31b4f788bb..32c13c901feb6e6e4ae476791ea548bd89c1d493 100644
--- a/base/server/man/man5/pki_default.cfg.5
+++ b/base/server/man/man5/pki_default.cfg.5
@@ -271,7 +271,24 @@ Replicate schema when the replication agreement is set up and the new instance (
 .TP
 .B pki_clone_replication_security
 .IP
-The type of security used for the replication data.  This can be set to SSL (using LDAPS), TLS, or None.  Defaults to None.  For SSL and TLS, SSL must be set up for the database instances beforehand.
+The transport mechanism for the replication data.  This can be set to SSL (using LDAPS), TLS, or LDAP.
+Defaults to LDAP.  This is the value that is set for the nsDS5ReplicaTransportInfo attribute in the
+replication agreements created on the master and clone database instances.  For SSL and TLS, SSL must be
+set up for the database instances beforehand.
+.TP
+.B pki_clone_replication_bind_method
+.IP
+The method used by the replication user to authenticate when performing replication.
+This is the value that will be set as the nsDS5ReplicaBindMethod in the replication
+agreements.  Defaults to "Simple".  See directory server documentation for other
+options (such as "SASL/GSSAPI"), including whatever setup needs to be completed
+before replication is initiated.
+.TP
+.B pki_clone_replication_bind_group
+.IP
+Group of replication agents.  Defaults to None.  This is the DN that will be specified as the
+value of the nsds5replicabinddngroup attribute in the replica configuration.  If this parameter
+is not set, then a replication bind user will be created and used instead.
 .TP
 .B pki_master_hostname, pki_master_https_port, pki_clone_uri
 .IP
diff --git a/base/server/man/man8/pkispawn.8 b/base/server/man/man8/pkispawn.8
index c2ab93ed252d40038126db22a6079178d22e0448..634d0db8928ed072a96bed2c794407a368bbd04e 100644
--- a/base/server/man/man8/pkispawn.8
+++ b/base/server/man/man8/pkispawn.8
@@ -441,6 +441,17 @@ clone# semanage -a -t pki_tomcat_cert_t /root/backup_keys.p12\fP
 .fi
 
 .PP
+In the above configuration, the installer will set up unencrypted replication
+agreements using the LDAP port.  The installer will create default users that
+authenticate using simple bind.  There are many more options available for the
+configuration of the replication agreements, though, both for transport and for
+authentication.  For transport, options also include encrypting the data on the
+LDAP port using startTLS or using encrypted LDAPS.  For authentication, it is
+possible to specify the bind method (GSSAPI for example) and a bind group DN
+(rather than simply using a bind user).  Details are provided in
+\fBpki_default.cfg\fR(5) under the \fBCLONE PARAMETERS\fR section.
+
+.PP
 .SS Installing a KRA or TKS clone
 .BR
 .PP
diff --git a/base/server/python/pki/server/deployment/pkihelper.py b/base/server/python/pki/server/deployment/pkihelper.py
index 5bc4ffab814891aadf0508d0ae20bb8cc315bc90..441c6186a7fbc9c0fc75b86bfdb2f800e6298a3d 100644
--- a/base/server/python/pki/server/deployment/pkihelper.py
+++ b/base/server/python/pki/server/deployment/pkihelper.py
@@ -4298,6 +4298,13 @@ class ConfigClient:
             data.cloneReplicationPort = \
                 self.mdict['pki_clone_replication_clone_port']
         data.setupReplication = self.mdict['pki_clone_setup_replication']
+        if self.mdict['pki_clone_replication_bind_group']:
+            data.replicationBindGroup = (
+                self.mdict['pki_clone_replication_bind_group'])
+
+        if self.mdict['pki_clone_replication_bind_method']:
+            data.replicationBindMethod = (
+                self.mdict['pki_clone_replication_bind_method'])
 
     def set_hierarchy_parameters(self, data):
         if self.subsystem == "CA":
-- 
1.9.3

