From cdd21834e5ccdda5f80db31f2b1fa32b851d676a Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Aug 25 2020 19:05:53 +0000 Subject: [PATCH 1/4] Issue 51156 - RFE - add bootstrap credentials to repl agreement Description: When using Bind DN Groups for a replication agreement authentication there are cases where the group is not present, or is outdated. In such cases having bootstrap credentials can allow replication to start working again. New replication sessions will always try and use the default credentials first. relates: https://pagure.io/389-ds-base/issue/51156 Reviewed by: ? --- diff --git a/dirsrvtests/tests/suites/replication/repl_agmt_bootstrap_test.py b/dirsrvtests/tests/suites/replication/repl_agmt_bootstrap_test.py new file mode 100644 index 0000000..01c1f10 --- /dev/null +++ b/dirsrvtests/tests/suites/replication/repl_agmt_bootstrap_test.py @@ -0,0 +1,118 @@ +import logging +import pytest +import os +import time +from lib389._constants import DEFAULT_SUFFIX +from lib389.topologies import topology_m2 as topo +from lib389.replica import BootstrapReplicationManager, Replicas +from lib389.idm.user import TEST_USER_PROPERTIES, UserAccounts, UserAccount +from lib389.idm.group import Group + +DEBUGGING = os.getenv("DEBUGGING", default=False) +if DEBUGGING: + logging.getLogger(__name__).setLevel(logging.DEBUG) +else: + logging.getLogger(__name__).setLevel(logging.INFO) +log = logging.getLogger(__name__) + +BOOTSTRAP_MGR_DN = 'uid=replication manager,cn=config' +BOOTSTRAP_MGR_PWD = 'boostrap_manager_password' +BIND_GROUP_DN = 'cn=replication_managers,' + DEFAULT_SUFFIX + + +def test_repl_agmt_bootstrap_credentials(topo): + """Test that the agreement bootstrap credentials works if the default + credentials fail for some reason. + + :id: 38c8095c-d958-415a-b602-74854b7882b3 + :setup: 2 Master Instances + :steps: + 1. Change the bind dn group member passwords + 2. Verify replication is not working + 3. Create a new repl manager on master 2 for bootstrapping + 4. Add bootstrap credentials to agmt on master 1 + 5. Verify replication is now working with bootstrap creds + 6. Trigger new repl session and default credentials are used first + :expectedresults: + 1. Success + 2. Success + 3. Success + 4. Success + 5. Success + 6. Success + """ + + # Gather all of our objects for the test + m1 = topo.ms["master1"] + m2 = topo.ms["master2"] + master1_replica = Replicas(m1).get(DEFAULT_SUFFIX) + master2_replica = Replicas(m2).get(DEFAULT_SUFFIX) + master2_users = UserAccounts(m2, DEFAULT_SUFFIX) + m1_agmt = master1_replica.get_agreements().list()[0] + num_of_original_users = len(master2_users.list()) + + # Change the member's passwords which should break replication + bind_group = Group(m2, dn=BIND_GROUP_DN) + members = bind_group.list_members() + for member_dn in members: + member = UserAccount(m2, dn=member_dn) + member.replace('userPassword', 'not_right') + time.sleep(3) + m1_agmt.pause() + m1_agmt.resume() + + # Verify replication is not working, a new user should not be replicated + users = UserAccounts(m1, DEFAULT_SUFFIX) + test_user = users.ensure_state(properties=TEST_USER_PROPERTIES) + time.sleep(3) + assert len(master2_users.list()) == num_of_original_users + + # Create a repl manager on replica + repl_mgr = BootstrapReplicationManager(m2, dn=BOOTSTRAP_MGR_DN) + mgr_properties = { + 'uid': 'replication manager', + 'cn': 'replication manager', + 'userPassword': BOOTSTRAP_MGR_PWD, + } + repl_mgr.create(properties=mgr_properties) + + # Update master 2 config + master2_replica.remove_all('nsDS5ReplicaBindDNGroup') + master2_replica.remove_all('nsDS5ReplicaBindDnGroupCheckInterval') + master2_replica.replace('nsDS5ReplicaBindDN', BOOTSTRAP_MGR_DN) + + # Add bootstrap credentials to master1 agmt, and restart agmt + m1_agmt.replace('nsds5ReplicaBootstrapTransportInfo', 'LDAP') + m1_agmt.replace('nsds5ReplicaBootstrapBindMethod', 'SIMPLE') + m1_agmt.replace('nsds5ReplicaBootstrapCredentials', BOOTSTRAP_MGR_PWD) + m1_agmt.replace('nsds5ReplicaBootstrapBindDN', BOOTSTRAP_MGR_DN) + m1_agmt.pause() + m1_agmt.resume() + + # Verify replication is working. The user should have been replicated + time.sleep(3) + assert len(master2_users.list()) > num_of_original_users + + # Finally check if the default credentials are used on the next repl + # session. Clear out the logs, and disable log buffering. Then + # trigger a replication update/session. + m1_agmt.pause() + m2.stop() + m2.deleteLog(m2.accesslog) # Clear out the logs + m2.start() + m2.config.set('nsslapd-accesslog-logbuffering', 'off') + m1_agmt.resume() + test_user.delete() + time.sleep(3) + + # We know if the default credentials are used it will fail (err=49) + results = m2.ds_access_log.match('.* err=49 .*') + assert len(results) > 0 + + +if __name__ == '__main__': + # Run isolated + # -s for DEBUG mode + CURRENT_FILE = os.path.realpath(__file__) + pytest.main(["-s", CURRENT_FILE]) + diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in index 19abcf8..3c70d30 100644 --- a/ldap/ldif/template-dse.ldif.in +++ b/ldap/ldif/template-dse.ldif.in @@ -234,6 +234,7 @@ nsslapd-plugintype: reverpwdstoragescheme nsslapd-pluginenabled: on nsslapd-pluginarg0: nsmultiplexorcredentials nsslapd-pluginarg1: nsds5ReplicaCredentials +nsslapd-pluginarg2: nsds5ReplicaBootstrapCredentials nsslapd-pluginid: aes-storage-scheme nsslapd-pluginprecedence: 1 diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif index 8db58de..27d863d 100644 --- a/ldap/schema/01core389.ldif +++ b/ldap/schema/01core389.ldif @@ -322,6 +322,10 @@ attributeTypes: ( 2.16.840.1.113730.3.1.602 NAME 'entrydn' DESC 'Internal databa attributeTypes: ( 2.16.840.1.113730.3.1.603 NAME 'dncomp' DESC 'Internal database attribute for each DN component' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.604 NAME 'parentid' DESC 'Internal database attribute for the parent ID of the entry' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.605 NAME 'entryid' DESC 'Internal database attribute for the ID of the entry' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2371 NAME 'nsDS5ReplicaBootstrapBindDN' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2372 NAME 'nsDS5ReplicaBootstrapCredentials' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2373 NAME 'nsDS5ReplicaBootstrapBindMethod' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2374 NAME 'nsDS5ReplicaBootstrapTransportInfo' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) # # objectclasses # @@ -333,7 +337,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.110 NAME 'nsMappingTree' DESC 'Netscape d objectClasses: ( 2.16.840.1.113730.3.2.104 NAME 'nsContainer' DESC 'Netscape defined objectclass' SUP top MUST ( CN ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.108 NAME 'nsDS5Replica' DESC 'Replication configuration objectclass' SUP top MUST ( nsDS5ReplicaRoot $ nsDS5ReplicaId ) MAY (cn $ nsds5ReplicaPreciseTombstonePurging $ nsds5ReplicaCleanRUV $ nsds5ReplicaAbortCleanRUV $ nsDS5ReplicaType $ nsDS5ReplicaBindDN $ nsDS5ReplicaBindDNGroup $ nsState $ nsDS5ReplicaName $ nsDS5Flags $ nsDS5Task $ nsDS5ReplicaReferral $ nsDS5ReplicaAutoReferral $ nsds5ReplicaPurgeDelay $ nsds5ReplicaTombstonePurgeInterval $ nsds5ReplicaChangeCount $ nsds5ReplicaLegacyConsumer $ nsds5ReplicaProtocolTimeout $ nsds5ReplicaBackoffMin $ nsds5ReplicaBackoffMax $ nsds5ReplicaReleaseTimeout $ nsDS5ReplicaBindDnGroupCheckInterval ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.113 NAME 'nsTombstone' DESC 'Netscape defined objectclass' SUP top MAY ( nstombstonecsn $ nsParentUniqueId $ nscpEntryDN ) X-ORIGIN 'Netscape Directory Server' ) -objectClasses: ( 2.16.840.1.113730.3.2.103 NAME 'nsDS5ReplicationAgreement' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsds5ReplicaCleanRUVNotified $ nsDS5ReplicaHost $ nsDS5ReplicaPort $ nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $ nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $ nsDS5ReplicatedAttributeListTotal $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh $ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $ nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $ nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress $ nsds5replicaLastInitEnd $ nsds5ReplicaEnabled $ nsds5replicaLastInitStart $ nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $ nsds5ReplicaStripAttrs $ nsds5replicaSessionPauseTime $ nsds5ReplicaProtocolTimeout $ nsds5ReplicaFlowControlWindow $ nsds5ReplicaFlowControlPause $ nsDS5ReplicaWaitForAsyncResults $ nsds5ReplicaIgnoreMissingChange) X-ORIGIN 'Netscape Directory Server' ) +objectClasses: ( 2.16.840.1.113730.3.2.103 NAME 'nsDS5ReplicationAgreement' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsds5ReplicaCleanRUVNotified $ nsDS5ReplicaHost $ nsDS5ReplicaPort $ nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $ nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $ nsDS5ReplicatedAttributeListTotal $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh $ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $ nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $ nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress $ nsds5replicaLastInitEnd $ nsds5ReplicaEnabled $ nsds5replicaLastInitStart $ nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $ nsds5ReplicaStripAttrs $ nsds5replicaSessionPauseTime $ nsds5ReplicaProtocolTimeout $ nsds5ReplicaFlowControlWindow $ nsds5ReplicaFlowControlPause $ nsDS5ReplicaWaitForAsyncResults $ nsds5ReplicaIgnoreMissingChange $ nsDS5ReplicaBootstrapBindDN $ nsDS5ReplicaBootstrapCredentials $ nsDS5ReplicaBootstrapBindMethod $ nsDS5ReplicaBootstrapTransportInfo ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.39 NAME 'nsslapdConfig' DESC 'Netscape defined objectclass' SUP top MAY ( cn ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.317 NAME 'nsSaslMapping' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSaslMapRegexString $ nsSaslMapBaseDNTemplate $ nsSaslMapFilterTemplate ) MAY ( nsSaslMapPriority ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.43 NAME 'nsSNMP' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSNMPEnabled ) MAY ( nsSNMPOrganization $ nsSNMPLocation $ nsSNMPContact $ nsSNMPDescription $ nsSNMPName $ nsSNMPMasterHost $ nsSNMPMasterPort ) X-ORIGIN 'Netscape Directory Server' ) diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h index 4215f5a..06320bd 100644 --- a/ldap/servers/plugins/replication/repl5.h +++ b/ldap/servers/plugins/replication/repl5.h @@ -157,6 +157,10 @@ extern const char *type_replicaBackoffMin; extern const char *type_replicaBackoffMax; extern const char *type_replicaPrecisePurge; extern const char *type_replicaIgnoreMissingChange; +extern const char *type_nsds5ReplicaBootstrapBindDN; +extern const char *type_nsds5ReplicaBootstrapCredentials; +extern const char *type_nsds5ReplicaBootstrapBindMethod; +extern const char *type_nsds5ReplicaBootstrapTransportInfo; /* Attribute names for windows replication agreements */ extern const char *type_nsds7WindowsReplicaArea; @@ -385,9 +389,13 @@ int agmt_replicate_now(Repl_Agmt *ra); char *agmt_get_hostname(const Repl_Agmt *ra); int agmt_get_port(const Repl_Agmt *ra); uint32_t agmt_get_transport_flags(const Repl_Agmt *ra); +uint32_t agmt_get_bootstrap_transport_flags(const Repl_Agmt *ra); char *agmt_get_binddn(const Repl_Agmt *ra); +char *agmt_get_bootstrap_binddn(const Repl_Agmt *ra); struct berval *agmt_get_credentials(const Repl_Agmt *ra); +struct berval *agmt_get_bootstrap_credentials(const Repl_Agmt *ra); int agmt_get_bindmethod(const Repl_Agmt *ra); +int32_t agmt_get_bootstrap_bindmethod(const Repl_Agmt *ra); Slapi_DN *agmt_get_replarea(const Repl_Agmt *ra); int agmt_is_fractional(const Repl_Agmt *ra); int agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname); @@ -404,9 +412,11 @@ int agmt_set_ignoremissing_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); int agmt_set_busywaittime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); int agmt_set_pausetime_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); int agmt_set_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); +int32_t agmt_set_bootstrap_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); int agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); -int agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); -int agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); +int32_t agmt_set_bootstrap_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); +int agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap); +int agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap); int agmt_set_port_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); int agmt_set_host_from_entry(Repl_Agmt *ra, const Slapi_Entry *e); const char *agmt_get_long_name(const Repl_Agmt *ra); diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c index 2948270..80898ce 100644 --- a/ldap/servers/plugins/replication/repl5_agmt.c +++ b/ldap/servers/plugins/replication/repl5_agmt.c @@ -94,9 +94,9 @@ typedef struct repl5agmt struct changecounter **changecounters; /* changes sent/skipped since server start up */ int64_t num_changecounters; int64_t max_changecounters; - time_t last_update_start_time; /* Local start time of last update session */ - time_t last_update_end_time; /* Local end time of last update session */ - char last_update_status[STATUS_LEN]; /* Status of last update. Format = numeric code textual description */ + time_t last_update_start_time; /* Local start time of last update session */ + time_t last_update_end_time; /* Local end time of last update session */ + char last_update_status[STATUS_LEN]; /* Status of last update. Format = numeric code textual description */ char last_update_status_json[STATUS_LEN]; PRBool update_in_progress; PRBool is_enabled; @@ -129,14 +129,19 @@ typedef struct repl5agmt * modifiersname, modifytimestamp, internalModifiersname, internalModifyTimestamp, etc */ int64_t agreement_type; Slapi_Counter *protocol_timeout; - char *maxcsn; /* agmt max csn */ - int64_t flowControlWindow; /* This is the maximum number of entries sent without acknowledgment */ - int64_t flowControlPause; /* When nb of not acknowledged entries overpass totalUpdateWindow - * This is the duration (in msec) that the RA will pause before sending the next entry */ - int64_t ignoreMissingChange; /* if set replication will try to continue even if change cannot be found in changelog */ - Slapi_RWLock *attr_lock; /* RW lock for all the stripped attrs */ - int64_t WaitForAsyncResults; /* Pass to DS_Sleep(PR_MillisecondsToInterval(WaitForAsyncResults)) - * in repl5_inc_waitfor_async_results */ + char *maxcsn; /* agmt max csn */ + int64_t flowControlWindow; /* This is the maximum number of entries sent without acknowledgment */ + int64_t flowControlPause; /* When nb of not acknowledged entries overpass totalUpdateWindow + * This is the duration (in msec) that the RA will pause before sending the next entry */ + int64_t ignoreMissingChange; /* if set replication will try to continue even if change cannot be found in changelog */ + Slapi_RWLock *attr_lock; /* RW lock for all the stripped attrs */ + int64_t WaitForAsyncResults; /* Pass to DS_Sleep(PR_MillisecondsToInterval(WaitForAsyncResults)) + * in repl5_inc_waitfor_async_results */ + char *bootstrapBindDN; /* Bootstrap bind dn */ + struct berval *bootstrapCreds; /* Bootstrap credentials */ + int64_t bootstrapBindmethod; /* Bootstrap Bind Method: simple, TLS, client auth, etc */ + uint32_t bootstrapTransportFlags; /* Bootstrap Transport Info: LDAPS, StartTLS, etc. */ + } repl5agmt; /* Forward declarations */ @@ -144,7 +149,9 @@ void agmt_delete(void **rap); static void update_window_state_change_callback(void *arg, PRBool opened); static int get_agmt_status(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, int *returncode, char *returntext, void *arg); static int agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); +static int32_t agmt_set_bootstrap_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); static int agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); +static int32_t agmt_set_bootstrap_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e); static ReplicaId agmt_maxcsn_get_rid(char *maxcsn); static void agmt_replica_reset_ignoremissing(const Repl_Agmt *agmt); @@ -309,10 +316,9 @@ agmt_new_from_entry(Slapi_Entry *e) if (NULL == ra->binddn) { ra->binddn = slapi_ch_strdup(""); } + /* Credentials to use when binding. */ - ra->creds = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); - ra->creds->bv_val = NULL; - ra->creds->bv_len = 0; + ra->creds = (struct berval *)slapi_ch_calloc(1, sizeof(struct berval)); if (slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr) == 0) { Slapi_Value *sval; if (slapi_attr_first_value(sattr, &sval) == 0) { @@ -325,6 +331,22 @@ agmt_new_from_entry(Slapi_Entry *e) /* How to bind */ (void)agmt_set_bind_method_no_lock(ra, e); + /* Process bootstrap settings */ + ra->bootstrapBindDN = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBootstrapBindDN); + ra->bootstrapCreds = (struct berval *)slapi_ch_calloc(1, sizeof(struct berval)); + if (slapi_entry_attr_find(e, type_nsds5ReplicaBootstrapCredentials, &sattr) == 0) { + Slapi_Value *sval; + if (slapi_attr_first_value(sattr, &sval) == 0) { + const struct berval *bv = slapi_value_get_berval(sval); + if (NULL != bv) { + slapi_ber_bvcpy(ra->bootstrapCreds, bv); + } + } + } + ra->bootstrapTransportFlags = 0; + (void)agmt_set_bootstrap_transportinfo_no_lock(ra, e); + (void)agmt_set_bootstrap_bind_method_no_lock(ra, e); + /* timeout. */ ra->timeout = DEFAULT_TIMEOUT; if ((val = slapi_entry_attr_get_ref(e, type_nsds5ReplicaTimeout))) { @@ -614,6 +636,7 @@ agmt_delete(void **rap) slapi_rdn_free((Slapi_RDN **)&ra->rdn); slapi_ch_free_string(&ra->hostname); slapi_ch_free_string(&ra->binddn); + slapi_ch_free_string(&ra->bootstrapBindDN); slapi_ch_array_free(ra->frac_attrs); slapi_ch_array_free(ra->frac_attrs_total); ra->frac_attr_total_defined = PR_FALSE; @@ -621,6 +644,9 @@ agmt_delete(void **rap) if (NULL != ra->creds) { ber_bvfree(ra->creds); } + if (NULL != ra->bootstrapCreds) { + ber_bvfree(ra->bootstrapCreds); + } if (NULL != ra->replarea) { /* * Get the replica for this agreement from the repl area @@ -923,7 +949,7 @@ agmt_get_port(const Repl_Agmt *ra) uint32_t agmt_get_transport_flags(const Repl_Agmt *ra) { - unsigned int return_value; + uint32_t return_value; PR_ASSERT(NULL != ra); PR_Lock(ra->lock); return_value = ra->transport_flags; @@ -931,6 +957,18 @@ agmt_get_transport_flags(const Repl_Agmt *ra) return return_value; } +uint32_t +agmt_get_bootstrap_transport_flags(const Repl_Agmt *ra) +{ + uint32_t return_value; + + PR_Lock(ra->lock); + return_value = ra->bootstrapTransportFlags; + PR_Unlock(ra->lock); + + return return_value; +} + /* * Return a copy of the bind dn to be used with this * agreement (may return NULL if no binddn is required, @@ -947,6 +985,18 @@ agmt_get_binddn(const Repl_Agmt *ra) return return_value; } +char * +agmt_get_bootstrap_binddn(const Repl_Agmt *ra) +{ + char *return_value; + + PR_Lock(ra->lock); + return_value = ra->bootstrapBindDN == NULL ? NULL : slapi_ch_strdup(ra->bootstrapBindDN); + PR_Unlock(ra->lock); + + return return_value; +} + /* * Return a copy of the credentials. */ @@ -965,6 +1015,22 @@ agmt_get_credentials(const Repl_Agmt *ra) return return_value; } +struct berval * +agmt_get_bootstrap_credentials(const Repl_Agmt *ra) +{ + struct berval *return_value; + + PR_Lock(ra->lock); + return_value = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); + return_value->bv_val = (char *)slapi_ch_malloc(ra->bootstrapCreds->bv_len + 1); + return_value->bv_len = ra->bootstrapCreds->bv_len; + memcpy(return_value->bv_val, ra->bootstrapCreds->bv_val, ra->bootstrapCreds->bv_len); + return_value->bv_val[return_value->bv_len] = '\0'; /* just in case */ + PR_Unlock(ra->lock); + + return return_value; +} + int agmt_get_bindmethod(const Repl_Agmt *ra) { @@ -976,6 +1042,18 @@ agmt_get_bindmethod(const Repl_Agmt *ra) return return_value; } +int32_t +agmt_get_bootstrap_bindmethod(const Repl_Agmt *ra) +{ + int32_t return_value; + + PR_Lock(ra->lock); + return_value = ra->bootstrapBindmethod; + PR_Unlock(ra->lock); + + return return_value; +} + /* * Return a copy of the dn at the top of the replicated area. */ @@ -1248,6 +1326,33 @@ agmt_set_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) return return_value; } +int32_t +agmt_set_bootstrap_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int32_t return_value = 0; + + slapi_entry_attr_find(e, type_nsds5ReplicaBootstrapCredentials, &sattr); + PR_Lock(ra->lock); + slapi_ber_bvdone(ra->bootstrapCreds); + if (NULL != sattr) { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) { + const struct berval *bv = slapi_value_get_berval(sval); + slapi_ber_bvcpy(ra->bootstrapCreds, bv); + } + } + /* If no credentials set, set to zero-length string */ + if (ra->bootstrapCreds->bv_val == NULL) { + ra->bootstrapCreds->bv_val = slapi_ch_strdup(""); + } + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + + return return_value; +} + /* * Set or reset the DN used to bind to the remote replica. * @@ -1262,7 +1367,7 @@ agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) PR_ASSERT(NULL != ra); slapi_entry_attr_find(e, type_nsds5ReplicaBindDN, &sattr); PR_Lock(ra->lock); - slapi_ch_free((void **)&ra->binddn); + slapi_ch_free_string(&ra->binddn); ra->binddn = NULL; if (NULL != sattr) { Slapi_Value *sval = NULL; @@ -1281,6 +1386,34 @@ agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) return return_value; } +int32_t +agmt_set_bootstrap_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +{ + Slapi_Attr *sattr = NULL; + int32_t return_value = 0; + + slapi_entry_attr_find(e, type_nsds5ReplicaBootstrapBindDN, &sattr); + PR_Lock(ra->lock); + slapi_ch_free_string(&ra->bootstrapBindDN); + ra->bootstrapBindDN = NULL; + if (NULL != sattr) { + Slapi_Value *sval = NULL; + slapi_attr_first_value(sattr, &sval); + if (NULL != sval) { + const char *val = slapi_value_get_string(sval); + ra->bootstrapBindDN = slapi_ch_strdup(val); + } + } + /* If no BindDN set, set to zero-length string */ + if (ra->bootstrapBindDN == NULL) { + ra->bootstrapBindDN = slapi_ch_strdup(""); + } + PR_Unlock(ra->lock); + prot_notify_agmt_changed(ra->protocol, ra->long_name); + + return return_value; +} + /* * Reset the port number of the remote replica. * @@ -1705,8 +1838,27 @@ agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) return return_value; } +/* Set the bootstrap bind method, we only allow SIMPLE or SSLClientAuth */ +static int32_t +agmt_set_bootstrap_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) +{ + const char *tmpstr = NULL; + + tmpstr = slapi_entry_attr_get_ref((Slapi_Entry *)e, type_nsds5ReplicaBootstrapBindMethod); + if (NULL == tmpstr || strcasecmp(tmpstr, "SIMPLE") == 0) { + ra->bootstrapBindmethod = BINDMETHOD_SIMPLE_AUTH; + } else if (strcasecmp(tmpstr, "SSLCLIENTAUTH") == 0) { + ra->bootstrapBindmethod = BINDMETHOD_SSL_CLIENTAUTH; + } else { + return -1; + } + + return 0; +} + + int -agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap) { int return_value = 0; @@ -1716,7 +1868,11 @@ agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) PR_Unlock(ra->lock); return return_value; } - return_value = agmt_set_bind_method_no_lock(ra, e); + if (bootstrap) { + return_value = agmt_set_bootstrap_bind_method_no_lock(ra, e); + } else { + return_value = agmt_set_bind_method_no_lock(ra, e); + } PR_Unlock(ra->lock); prot_notify_agmt_changed(ra->protocol, ra->long_name); return return_value; @@ -1746,6 +1902,25 @@ agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) return (rc); } +static int32_t +agmt_set_bootstrap_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e) +{ + const char *tmpstr; + + tmpstr = slapi_entry_attr_get_ref((Slapi_Entry *)e, type_nsds5ReplicaBootstrapTransportInfo); + if (!tmpstr || !strcasecmp(tmpstr, "LDAP")) { + ra->bootstrapTransportFlags = 0; + } else if (strcasecmp(tmpstr, "SSL") == 0 || strcasecmp(tmpstr, "LDAPS") == 0) { + ra->bootstrapTransportFlags = TRANSPORT_FLAG_LDAPS; + } else if (strcasecmp(tmpstr, "TLS") == 0 || strcasecmp(tmpstr, "StartTLS") == 0) { + ra->bootstrapTransportFlags = TRANSPORT_FLAG_STARTTLS; + } else { + return -1; + } + + return 0; +} + int agmt_set_WaitForAsyncResults(Repl_Agmt *ra, const Slapi_Entry *e) { @@ -1768,7 +1943,7 @@ agmt_get_WaitForAsyncResults(Repl_Agmt *ra) } int -agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) +agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap) { int return_value = 0; @@ -1778,6 +1953,11 @@ agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e) PR_Unlock(ra->lock); return return_value; } + if (bootstrap) { + return_value = agmt_set_bootstrap_transportinfo_no_lock(ra, e); + } else { + return_value = agmt_set_transportinfo_no_lock(ra, e); + } return_value = agmt_set_transportinfo_no_lock(ra, e); PR_Unlock(ra->lock); prot_notify_agmt_changed(ra->protocol, ra->long_name); diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c index f0ef612..18b641f 100644 --- a/ldap/servers/plugins/replication/repl5_agmtlist.c +++ b/ldap/servers/plugins/replication/repl5_agmtlist.c @@ -342,6 +342,16 @@ agmtlist_modify_callback(Slapi_PBlock *pb, rc = SLAPI_DSE_CALLBACK_ERROR; } } else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBootstrapCredentials)) { + /* New replica bootstrap credentials */ + if (agmt_set_bootstrap_credentials_from_entry(agmt, e) != 0) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - " + "Failed to update bootstrap credentials for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } else if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaTimeout)) { /* New replica timeout */ if (agmt_set_timeout_from_entry(agmt, e) != 0) { @@ -411,6 +421,17 @@ agmtlist_modify_callback(Slapi_PBlock *pb, *returncode = LDAP_OPERATIONS_ERROR; rc = SLAPI_DSE_CALLBACK_ERROR; } + } else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBootstrapBindDN)) { + /* New bootstrap Bind DN */ + if (agmt_set_bootstrap_binddn_from_entry(agmt, e) != 0) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback- " + "Failed to update bind DN for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } else if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaHost)) { /* New replica host */ if (agmt_set_host_from_entry(agmt, e) != 0) { @@ -447,19 +468,40 @@ agmtlist_modify_callback(Slapi_PBlock *pb, } else if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5TransportInfo)) { /* New Transport info */ - if (agmt_set_transportinfo_from_entry(agmt, e) != 0) { + if (agmt_set_transportinfo_from_entry(agmt, e, PR_FALSE /* get default value */) != 0) { slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - " - "Failed to update transport info for agreement %s\n", - agmt_get_long_name(agmt)); + "Failed to update transport info for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBootstrapTransportInfo)) + { + /* Bootstrap Transport info */ + if (agmt_set_transportinfo_from_entry(agmt, e, PR_TRUE /* get bootstrap value */) != 0) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - " + "Failed to update bootstrap transport info for agreement %s\n", + agmt_get_long_name(agmt)); *returncode = LDAP_OPERATIONS_ERROR; rc = SLAPI_DSE_CALLBACK_ERROR; } } else if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaBindMethod)) { - if (agmt_set_bind_method_from_entry(agmt, e) != 0) { + if (agmt_set_bind_method_from_entry(agmt, e, PR_FALSE /* get default value */) != 0) { slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - " - "Failed to update bind method for agreement %s\n", - agmt_get_long_name(agmt)); + "Failed to update bind method for agreement %s\n", + agmt_get_long_name(agmt)); + *returncode = LDAP_OPERATIONS_ERROR; + rc = SLAPI_DSE_CALLBACK_ERROR; + } + } else if (slapi_attr_types_equivalent(mods[i]->mod_type, + type_nsds5ReplicaBootstrapBindMethod)) { + /* Bootstrap bind method */ + if (agmt_set_bind_method_from_entry(agmt, e, PR_TRUE /* get bootstrap value */) != 0) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - " + "Failed to update bootstrap bind method for agreement %s\n", + agmt_get_long_name(agmt)); *returncode = LDAP_OPERATIONS_ERROR; rc = SLAPI_DSE_CALLBACK_ERROR; } diff --git a/ldap/servers/plugins/replication/repl5_connection.c b/ldap/servers/plugins/replication/repl5_connection.c index 7488f14..cf57c21 100644 --- a/ldap/servers/plugins/replication/repl5_connection.c +++ b/ldap/servers/plugins/replication/repl5_connection.c @@ -73,6 +73,7 @@ void supplier_learn_new_definitions(struct berval **objectclasses, struct berval static LDAPControl manageDSAITControl = {LDAP_CONTROL_MANAGEDSAIT, {0, ""}, '\0'}; static int attribute_string_value_present(LDAP *ld, LDAPMessage *entry, const char *type, const char *value); static int bind_and_check_pwp(Repl_Connection *conn, char *binddn, char *password); +static int32_t conn_connect_with_bootstrap(Repl_Connection *conn, PRBool bootstrap); static int s_debug_timeout = 0; static int s_debug_level = 0; @@ -1046,12 +1047,7 @@ conn_start_linger(Repl_Connection *conn) ConnResult conn_connect(Repl_Connection *conn) { - int optdata; - int secure = 0; - char *binddn = NULL; - struct berval *creds = NULL; ConnResult return_value = CONN_OPERATION_SUCCESS; - int pw_ret = 1; PR_Lock(conn->lock); @@ -1060,38 +1056,84 @@ conn_connect(Repl_Connection *conn) PR_Unlock(conn->lock); return return_value; } + return_value = conn_connect_with_bootstrap(conn, PR_FALSE); + if (return_value != CONN_OPERATION_SUCCESS && + (conn->last_ldap_error == LDAP_INVALID_CREDENTIALS || + conn->last_ldap_error == LDAP_INAPPROPRIATE_AUTH || + conn->last_ldap_error == LDAP_NO_SUCH_OBJECT)) + { + /* try the bootstrap credentials */ + return_value = conn_connect_with_bootstrap(conn, PR_TRUE); + } - if (conn->flag_agmt_changed) { - /* So far we cannot change Hostname and Port */ - /* slapi_ch_free((void **)&conn->hostname); */ - /* conn->hostname = agmt_get_hostname(conn->agmt); */ - /* conn->port = agmt_get_port(conn->agmt); */ - slapi_ch_free((void **)&conn->binddn); + PR_Unlock(conn->lock); + + return return_value; +} + +/* + * There are cases when using bind DN group credentials that the consumer does + * not have the Bind DN group, or it is outdated. In those cases we can try + * use bootstrap credentials (if set) to attempt to authenticate again. Each + * new connection will always try the default credentials first, but if it + * fails it will try the bootstrap settings as a backup to get things synched + * up again, or "bootstrapped". + */ +static int32_t +conn_connect_with_bootstrap(Repl_Connection *conn, PRBool bootstrap) +{ + int32_t optdata; + int32_t secure = 0; + struct berval *creds = NULL; + ConnResult return_value = CONN_OPERATION_SUCCESS; + int32_t pw_ret = 1; + + if (bootstrap) { + /* default credentials failed, try the bootstrap creds */ + char *binddn = NULL; + + if((binddn = agmt_get_bootstrap_binddn(conn->agmt)) == NULL) { + /* There are no bootstrap settings, just return error */ + return CONN_OPERATION_FAILED; + } + slapi_ch_free_string(&conn->plain); + slapi_ch_free_string(&conn->binddn); + conn->binddn = binddn; + creds = agmt_get_bootstrap_credentials(conn->agmt); + conn->bindmethod = agmt_get_bootstrap_bindmethod(conn->agmt); + conn->transport_flags = agmt_get_bootstrap_transport_flags(conn->agmt); + } else { + slapi_ch_free_string(&conn->binddn); conn->binddn = agmt_get_binddn(conn->agmt); + creds = agmt_get_credentials(conn->agmt); conn->bindmethod = agmt_get_bindmethod(conn->agmt); conn->transport_flags = agmt_get_transport_flags(conn->agmt); + } + + if (conn->flag_agmt_changed) { + /* So far we cannot change Hostname */ conn->timeout.tv_sec = agmt_get_timeout(conn->agmt); conn->flag_agmt_changed = 0; conn->port = agmt_get_port(conn->agmt); /* port could be updated */ - slapi_ch_free((void **)&conn->plain); - } - creds = agmt_get_credentials(conn->agmt); + } if (conn->plain == NULL) { - char *plain = NULL; /* kexcoff: for reversible encryption */ /* We need to test the return code of pw_rever_decode in order to decide * if a free for plain will be needed (pw_ret == 0) or not (pw_ret != 0) */ - pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaCredentials); + if (bootstrap) { + pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaBootstrapCredentials); + } else { + pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaCredentials);; + } /* Pb occured in decryption: stop now, binding will fail */ if (pw_ret == -1) { slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "conn_connect - %s - Decoding of the credentials failed.\n", agmt_get_long_name(conn->agmt)); - return_value = CONN_OPERATION_FAILED; conn->last_ldap_error = LDAP_INVALID_CREDENTIALS; conn->state = STATE_DISCONNECTED; @@ -1099,11 +1141,11 @@ conn_connect(Repl_Connection *conn) } /* Else, does not mean that the plain is correct, only means the we had no internal decoding pb */ conn->plain = slapi_ch_strdup(plain); - if (!pw_ret) - slapi_ch_free((void **)&plain); + if (!pw_ret) { + slapi_ch_free_string(&plain); + } } - /* ugaston: if SSL has been selected in the replication agreement, SSL client * initialisation should be done before ever trying to open any connection at all. */ @@ -1127,7 +1169,6 @@ conn_connect(Repl_Connection *conn) if (return_value == CONN_OPERATION_SUCCESS) { /* Now we initialize the LDAP Structure and set options */ - slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, "conn_connect - %s - Trying %s%s slapi_ldap_init_ext\n", agmt_get_long_name(conn->agmt), @@ -1153,13 +1194,10 @@ conn_connect(Repl_Connection *conn) goto done; } - /* slapi_ch_strdup is OK with NULL strings */ - binddn = slapi_ch_strdup(conn->binddn); - slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, "conn_connect - %s - binddn = %s, passwd = %s\n", agmt_get_long_name(conn->agmt), - binddn ? binddn : "NULL", creds->bv_val ? creds->bv_val : "NULL"); + conn->binddn ? conn->binddn : "NULL", creds->bv_val ? creds->bv_val : "NULL"); /* Set some options for the connection. */ optdata = LDAP_DEREF_NEVER; /* Don't dereference aliases */ @@ -1177,7 +1215,7 @@ conn_connect(Repl_Connection *conn) conn->last_operation = CONN_BIND; } - if (bind_and_check_pwp(conn, binddn, conn->plain) == CONN_OPERATION_FAILED) { + if (bind_and_check_pwp(conn, conn->binddn, conn->plain) == CONN_OPERATION_FAILED) { conn->last_ldap_error = slapi_ldap_get_lderrno(conn->ld, NULL, NULL); conn->state = STATE_DISCONNECTED; return_value = CONN_OPERATION_FAILED; @@ -1189,17 +1227,17 @@ conn_connect(Repl_Connection *conn) done: ber_bvfree(creds); - creds = NULL; - - slapi_ch_free((void **)&binddn); + if (bootstrap) { + /* free "plain" so we use the default credentials on the next session */ + slapi_ch_free_string(&conn->plain); + } if (return_value == CONN_OPERATION_SUCCESS) { conn->last_ldap_error = LDAP_SUCCESS; conn->state = STATE_CONNECTED; } else { close_connection_internal(conn); } - PR_Unlock(conn->lock); return return_value; } @@ -1861,8 +1899,7 @@ bind_and_check_pwp(Repl_Connection *conn, char *binddn, char *password) } if (ctrls) { - int i; - for (i = 0; ctrls[i] != NULL; ++i) { + for (size_t i = 0; ctrls[i] != NULL; ++i) { if (!(strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRED))) { /* Bind is successfull but password has expired */ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c index c615c77..000777f 100644 --- a/ldap/servers/plugins/replication/repl_globals.c +++ b/ldap/servers/plugins/replication/repl_globals.c @@ -113,6 +113,10 @@ const char *type_nsds5ReplicaFlowControlWindow = "nsds5ReplicaFlowControlWindow" const char *type_nsds5ReplicaFlowControlPause = "nsds5ReplicaFlowControlPause"; const char *type_nsds5WaitForAsyncResults = "nsds5ReplicaWaitForAsyncResults"; const char *type_replicaIgnoreMissingChange = "nsds5ReplicaIgnoreMissingChange"; +const char *type_nsds5ReplicaBootstrapBindDN = "nsds5ReplicaBootstrapBindDN"; +const char *type_nsds5ReplicaBootstrapCredentials = "nsds5ReplicaBootstrapCredentials"; +const char *type_nsds5ReplicaBootstrapBindMethod = "nsds5ReplicaBootstrapBindMethod"; +const char *type_nsds5ReplicaBootstrapTransportInfo = "nsds5ReplicaBootstrapTransportInfo"; /* windows sync specific attributes */ const char *type_nsds7WindowsReplicaArea = "nsds7WindowsReplicaSubtree"; From 9aa3482629544899be658091d8254b9b9cc30438 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Aug 25 2020 19:07:09 +0000 Subject: [PATCH 2/4] Issue 51156 - RFE - add bootstrap credentials to repl agreement Description: When using Bind DN Groups for a replication agreement authentication there are cases where the group is not relates: https://pagure.io/389-ds-base/issue/51156 Reviewed by: ? --- diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py index f6cfc10..3af18d6 100644 --- a/src/lib389/lib389/cli_conf/replication.py +++ b/src/lib389/lib389/cli_conf/replication.py @@ -49,6 +49,10 @@ arg_to_attr = { 'bind_dn': 'nsds5replicabinddn', 'bind_passwd': 'nsds5replicacredentials', 'bind_method': 'nsds5replicabindmethod', + 'bootstrap_conn_protocol': 'nsds5replicabootstraptransportinfo', + 'bootstrap_bind_dn': 'nsds5replicabootstrapbinddn', + 'bootstrap_bind_passwd': 'nsds5replicabootstrapcredentials', + 'bootstrap_bind_method': 'nsds5replicabootstrapbindmethod', 'frac_list': 'nsds5replicatedattributelist', 'frac_list_total': 'nsds5replicatedattributelisttotal', 'strip_list': 'nsds5replicastripattrs', @@ -724,6 +728,24 @@ def add_agmt(inst, basedn, log, args): if args.strip_list is not None: properties['nsds5replicastripattrs'] = args.strip_list + # Handle the optional bootstrap settings + if args.bootstrap_bind_dn is not None: + if not is_a_dn(args.bootstrap_bind_dn): + raise ValueError("The replica bootstrap bind DN is not a valid DN") + properties['nsDS5ReplicaBootstrapBindDN'] = args.bootstrap_bind_dn + if args.bootstrap_bind_passwd is not None: + properties['nsDS5ReplicaBootstrapCredentials'] = args.bootstrap_bind_passwd + if args.bootstrap_bind_method is not None: + bs_bind_method = args.bootstrap_bind_method.lower() + if bs_bind_method != "simple" and bs_bind_method != "sslclientauth": + raise ValueError('Bootstrap bind method can only be "SIMPLE" or "SSLCLIENTAUTH"') + properties['nsDS5ReplicaBootstrapBindMethod'] = args.bootstrap_bind_method + if args.bootstrap_conn_protocol is not None: + bootstrap_conn_protocol = args.bootstrap_conn_protocol.lower() + if bootstrap_conn_protocol != "ldap" and bootstrap_conn_protocol != "ldaps" and bootstrap_conn_protocol != "starttls": + raise ValueError('Bootstrap connection protocol can only be "LDAP", "LDAPS", or "STARTTLS"') + properties['nsDS5ReplicaBootstrapTransportInfo'] = args.bootstrap_conn_protocol + # We do need the bind dn and credentials for none-sasl bind methods if (bind_method in ('simple', 'sslclientauth')) and (args.bind_dn is None or args.bind_passwd is None): raise ValueError("You need to set the bind dn (--bind-dn) and the password (--bind-passwd) for bind method ({})".format(bind_method)) @@ -795,6 +817,14 @@ def set_agmt(inst, basedn, log, args): for frac_attr in value.split(): frac_list += " " + frac_attr value = frac_list + elif attr == 'nsds5replicabootstrapbindmethod': + bs_bind_method = value.lower() + if bs_bind_method != "simple" and bs_bind_method != "sslclientauth": + raise ValueError('Bootstrap bind method can only be "SIMPLE" or "SSLCLIENTAUTH"') + elif attr == 'nsds5replicabootstraptransportinfo': + bs_conn_protocol = value.lower() + if bs_conn_protocol != "ldap" and bs_conn_protocol != "ldaps" and bs_conn_protocol != "starttls": + raise ValueError('Bootstrap bind method can only be "LDAP", "LDAPS, or "STARTTLS"') modlist.append((attr, value)) if len(modlist) > 0: @@ -1332,7 +1362,7 @@ def create_parser(subparsers): agmt_add_parser.add_argument('--strip-list', help="A list of attributes that are removed from updates only if the event " "would otherwise be empty. Typically this is set to \"modifiersname\" and \"modifytimestmap\"") agmt_add_parser.add_argument('--schedule', help="Sets the replication update schedule: 'HHMM-HHMM DDDDDDD' D = 0-6 (Sunday - Saturday).") - agmt_add_parser.add_argument('--conn-timeout', help="The timeout used for replicaton connections") + agmt_add_parser.add_argument('--conn-timeout', help="The timeout used for replication connections") agmt_add_parser.add_argument('--protocol-timeout', help="A timeout in seconds on how long to wait before stopping " "replication when the server is under load") agmt_add_parser.add_argument('--wait-async-results', help="The amount of time in milliseconds the server waits if " @@ -1343,6 +1373,10 @@ def create_parser(subparsers): agmt_add_parser.add_argument('--session-pause-time', help="The amount of time in seconds a supplier should wait between update sessions.") agmt_add_parser.add_argument('--flow-control-window', help="Sets the maximum number of entries and updates sent by a supplier, which are not acknowledged by the consumer.") agmt_add_parser.add_argument('--flow-control-pause', help="The time in milliseconds to pause after reaching the number of entries and updates set in \"--flow-control-window\"") + agmt_add_parser.add_argument('--bootstrap-bind-dn', help="An optional Bind DN the agreement can use to bootstrap initialization when bind groups are being used") + agmt_add_parser.add_argument('--bootstrap-bind-passwd', help="The bootstrap credentials for the Bind DN") + agmt_add_parser.add_argument('--bootstrap-conn-protocol', help="The replication bootstrap connection protocol: LDAP, LDAPS, or StartTLS") + agmt_add_parser.add_argument('--bootstrap-bind-method', help="The bind method: \"SIMPLE\", or \"SSLCLIENTAUTH\"") agmt_add_parser.add_argument('--init', action='store_true', default=False, help="Initialize the agreement after creating it.") # Set - Note can not use add's parent args because for "set" there are no "required=True" args @@ -1361,7 +1395,7 @@ def create_parser(subparsers): agmt_set_parser.add_argument('--strip-list', help="A list of attributes that are removed from updates only if the event " "would otherwise be empty. Typically this is set to \"modifiersname\" and \"modifytimestmap\"") agmt_set_parser.add_argument('--schedule', help="Sets the replication update schedule: 'HHMM-HHMM DDDDDDD' D = 0-6 (Sunday - Saturday).") - agmt_set_parser.add_argument('--conn-timeout', help="The timeout used for replicaton connections") + agmt_set_parser.add_argument('--conn-timeout', help="The timeout used for replication connections") agmt_set_parser.add_argument('--protocol-timeout', help="A timeout in seconds on how long to wait before stopping " "replication when the server is under load") agmt_set_parser.add_argument('--wait-async-results', help="The amount of time in milliseconds the server waits if " @@ -1371,6 +1405,10 @@ def create_parser(subparsers): agmt_set_parser.add_argument('--session-pause-time', help="The amount of time in seconds a supplier should wait between update sessions.") agmt_set_parser.add_argument('--flow-control-window', help="Sets the maximum number of entries and updates sent by a supplier, which are not acknowledged by the consumer.") agmt_set_parser.add_argument('--flow-control-pause', help="The time in milliseconds to pause after reaching the number of entries and updates set in \"--flow-control-window\"") + agmt_set_parser.add_argument('--bootstrap-bind-dn', help="An optional Bind DN the agreement can use to bootstrap initialization when bind groups are being used") + agmt_set_parser.add_argument('--bootstrap-bind-passwd', help="The bootstrap credentials for the Bind DN") + agmt_set_parser.add_argument('--bootstrap-conn-protocol', help="The replication bootstrap connection protocol: LDAP, LDAPS, or StartTLS") + agmt_set_parser.add_argument('--bootstrap-bind-method', help="The bind method: \"SIMPLE\", or \"SSLCLIENTAUTH\"") # Get agmt_get_parser = agmt_subcommands.add_parser('get', help='Get replication configuration') From a858085fa08fe6063d41500e7c48441620bd6d43 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Aug 27 2020 18:36:40 +0000 Subject: [PATCH 3/4] Issue 51156 - RFE - add bootstrap credentials to repl agreement (UI update) Description: Add replication bootstrap settings to the UI. relates: https://pagure.io/389-ds-base/issue/51156 Reviewed by: ? --- diff --git a/src/cockpit/389-console/src/lib/replication/replAgmts.jsx b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx index ec1beef..23c7ff1 100644 --- a/src/cockpit/389-console/src/lib/replication/replAgmts.jsx +++ b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx @@ -35,6 +35,12 @@ export class ReplAgmts extends React.Component { agmtBindDN: "", agmtBindPW: "", agmtBindPWConfirm: "", + agmtBootstrap: false, + agmtBootstrapProtocol: "LDAP", + agmtBootstrapBindMethod: "SIMPLE", + agmtBootstrapBindDN: "", + agmtBootstrapBindPW: "", + agmtBootstrapBindPWConfirm: "", agmtStripAttrs: [], agmtFracAttrs: [], agmtFracInitAttrs: [], @@ -113,6 +119,7 @@ export class ReplAgmts extends React.Component { handleChange (e) { let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; + let attr = e.target.id; let time_val = ""; let valueErr = false; let errObj = this.state.errObj; @@ -123,10 +130,9 @@ export class ReplAgmts extends React.Component { if (value == "") { valueErr = true; } - errObj[e.target.id] = valueErr; + errObj[attr] = valueErr; if (e.target.name == "agmt-modal-edit") { let orig_attr = "_" + e.target.id; - let attr = e.target.id; all_good = false; if ((attr != 'agmtHost' && this.state.agmtHost != this.state._agmtHost) || (attr != 'agmtPort' && this.state.agmtPort != this.state._agmtPort) || @@ -134,6 +140,7 @@ export class ReplAgmts extends React.Component { (attr != 'agmtBindMethod' && this.state.agmtBindMethod != this.state._agmtBindMethod) || (attr != 'agmtProtocol' && this.state.agmtProtocol != this.state._agmtProtocol) || (attr != 'agmtSync' && this.state.agmtSync != this.state._agmtSync) || + (attr != 'agmtBootstrap' && this.state.agmtBootstrap != this.state._agmtBootstrap) || (attr != 'agmtStripAttrs' && !this.listEqual(this.state.agmtStripAttrs, this.state._agmtStripAttrs)) || (attr != 'agmtFracAttrs' && !this.listEqual(this.state.agmtFracAttrs, this.state._agmtFracAttrs)) || (attr != 'agmtFracInitAttrs' && !this.listEqual(this.state.agmtFracInitAttrs, this.state._agmtFracInitAttrs))) { @@ -150,6 +157,17 @@ export class ReplAgmts extends React.Component { all_good = true; } } + if (this.state._agmtBootstrap) { + if ((attr != 'agmtBootstrapBindDN' && this.state.agmtBootstrapBindDN != this.state._agmtBootstrapBindDN) || + (attr != 'agmtBootstrapBindPW' && this.state.agmtBootstrapBindPW != this.state._agmtBootstrapBindPW) || + (attr != 'agmtBootstrapBindPWConfirm' && this.state.agmtBootstrapBindPWConfirm != this.state._agmtBootstrapBindPWConfirm) || + (attr != 'agmtBootstrapBindMethod' && this.state.agmtBootstrapBindMethod != this.state._agmtBootstrapBindMethod) || + (attr != 'agmtBootstrapProtocol' && this.state.agmtBootstrapProtocol != this.state._agmtBootstrapProtocol)) { + all_good = true; + } else { + all_good = false; + } + } if (attr != 'agmtStripAttrs' && attr != 'agmtFracAttrs' && attr != 'agmtFracInitAttrs' && @@ -169,14 +187,14 @@ export class ReplAgmts extends React.Component { if (e.target.name.startsWith("agmt-modal")) { // Validate modal settings "live" - if (e.target.id == 'agmtName') { + if (attr == 'agmtName') { if (value == "") { all_good = false; } } else if (this.state.agmtName == "") { all_good = false; } - if (e.target.id == 'agmtHost') { + if (attr == 'agmtHost') { if (value == "") { all_good = false; } @@ -185,7 +203,7 @@ export class ReplAgmts extends React.Component { } else if (edit && value == this.state._agmtHost) { all_good = false; } - if (e.target.id == 'agmtPort') { + if (attr == 'agmtPort') { if (value == "") { all_good = false; } else if (!valid_port(value)) { @@ -196,7 +214,7 @@ export class ReplAgmts extends React.Component { } else if (this.state.agmtPort == "") { all_good = false; } - if (e.target.id == 'agmtBindDN') { + if (attr == 'agmtBindDN') { if (value == "") { all_good = false; } @@ -212,7 +230,7 @@ export class ReplAgmts extends React.Component { errObj['agmtBindDN'] = true; all_good = false; } - if (e.target.id == 'agmtBindPW') { + if (attr == 'agmtBindPW') { if (value == "") { all_good = false; } else if (value != this.state.agmtBindPWConfirm) { @@ -227,7 +245,7 @@ export class ReplAgmts extends React.Component { } else if (this.state.agmtBindPW == "") { all_good = false; } - if (e.target.id == 'agmtBindPWConfirm') { + if (attr == 'agmtBindPWConfirm') { if (value == "") { all_good = false; } else if (value != this.state.agmtBindPW) { @@ -242,7 +260,189 @@ export class ReplAgmts extends React.Component { } else if (this.state.agmtBindPWConfirm == "") { all_good = false; } - if (e.target.id == 'agmtSync') { + + // Check for conflicting bind methods verses connection protocol + if (attr == 'agmtBindMethod') { + if (value == "SSLCLIENTAUTH" && this.state.agmtProtocol == "LDAP") { + modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH"; + errObj['agmtBindMethod'] = true; + all_good = false; + } else if (value == "SASL/GSSAPI" && this.state.agmtProtocol == "LDAPS") { + // GSSAPI must be over LDAP, not LDAPS + modal_msg = "You must use the connection protocol LDAP if you choose the bind method SASL/GSSAPI"; + errObj['agmtBindMethod'] = true; + all_good = false; + } else { + errObj['agmtBindMethod'] = false; + errObj['agmtProtocol'] = false; + } + } else if (attr == 'agmtProtocol') { + if (value == "LDAP" && this.state.agmtBindMethod == "SSLCLIENTAUTH") { + modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH"; + errObj['agmtBindMethod'] = true; + all_good = false; + } else if (value == "LDAPS" && this.state.agmtBindMethod == "SASL/GSSAPI") { + // GSSAPI must be over LDAP, not LDAPS + modal_msg = "You must use the connection protocol LDAP if you choose the bind method SASL/GSSAPI"; + errObj['agmtBindMethod'] = true; + all_good = false; + } else { + errObj['agmtBindMethod'] = false; + errObj['agmtProtocol'] = false; + } + } else { + if (this.state.agmtBindMethod == "SSLCLIENTAUTH" && this.state.agmtProtocol == "LDAP") { + modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH"; + errObj['agmtBindMethod'] = true; + errObj['agmtProtocol'] = true; + all_good = false; + } else { + errObj['agmtBindMethod'] = false; + errObj['agmtProtocol'] = false; + } + } + // Handle the bootstrap settings. There is a lot going on here. If + // the Bind Method is SIMPLE we need a user password, if it's + // SSLCLIENTAUTH we do not need a password. We also have to enforce + // LDAPS is used for SSLCLIENTAUTH. This is similar to how we + // handle the agmt schedule settings. We always need to check all + // the bootstrap settings if one of the bootstrap settings is + // changed - so there is a lot of overlap of checks, and setting and + // unsetting the errObj, etc + if (attr == 'agmtBootstrap') { + if (value) { + if (this.state.agmtBootstrapBindMethod == "SIMPLE") { + if (this.state.agmtBootstrapBindPW == "" || this.state.agmtBootstrapBindPWConfirm == "") { + // Can't be empty + errObj['agmtBootstrapBindPW'] = true; + errObj['agmtBootstrapBindPWConfirm'] = true; + all_good = false; + } else if (this.state.agmtBootstrapBindPW != this.state.agmtBootstrapBindPWConfirm) { + // Must match + modal_msg = "Bootstrap Passwords Do Not Match"; + errObj['agmtBootstrapBindPW'] = true; + errObj['agmtBootstrapBindPWConfirm'] = true; + all_good = false; + } else { + errObj['agmtBootstrapProtocol'] = false; + errObj['agmtBootstrapBindMethod'] = false; + } + } else if (this.state.agmtBootstrapProtocol != "LDAPS") { + modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH"; + errObj['agmtBootstrapProtocol'] = true; + all_good = false; + } else { + // All good, reset the errObj + errObj['agmtBootstrapProtocol'] = false; + errObj['agmtBootstrapBindMethod'] = false; + } + if (this.state.agmtBootstrapBindDN == "" || !valid_dn(this.state.agmtBootstrapBindDN)) { + errObj['agmtBootstrapBindDN'] = true; + all_good = false; + } + } + } else if (this.state.agmtBootstrap) { + // Check all the bootstrap settings + if (attr == "agmtBootstrapBindDN") { + if (value == "" || !valid_dn(value)) { + errObj['agmtBootstrapBindDN'] = true; + all_good = false; + } else { + errObj['agmtBootstrapBindDN'] = false; + } + } else if (this.state.agmtBootstrapBindDN == "" || !valid_dn(this.state.agmtBootstrapBindDN)) { + errObj['agmtBootstrapBindDN'] = true; + all_good = false; + } else { + // No problems here, make sure the errObj is reset + errObj['agmtBootstrapBindDN'] = false; + } + + if (attr == 'agmtBootstrapBindMethod') { + // Adjusting the Bind Method, if SIMPLE then verify the + // passwords are set and correct + console.log("MARK proto2: ", this.state.agmtBootstrapProtocol); + if (value == "SIMPLE") { + if (this.state.agmtBootstrapBindPW == "" || this.state.agmtBootstrapBindPWConfirm == "") { + // Can't be empty + errObj['agmtBootstrapBindPW'] = true; + errObj['agmtBootstrapBindPWConfirm'] = true; + all_good = false; + } else if (this.state.agmtBootstrapBindPW != this.state.agmtBootstrapBindPWConfirm) { + // Must match + modal_msg = "Bootstrap Passwords Do Not Match"; + errObj['agmtBootstrapBindPW'] = true; + errObj['agmtBootstrapBindPWConfirm'] = true; + all_good = false; + } + } else { + // Not SIMPLE, ignore the passwords and reset errObj + errObj['agmtBootstrapBindPW'] = false; + errObj['agmtBootstrapBindPWConfirm'] = false; + if (this.state.agmtBootstrapProtocol != "LDAPS") { + modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH"; + errObj['agmtBootstrapBindMethod'] = true; + all_good = false; + } else { + // All good, reset the errObj + errObj['agmtBootstrapProtocol'] = false; + errObj['agmtBootstrapBindMethod'] = false; + } + } + } else if (this.state.agmtBootstrapBindMethod == "SIMPLE") { + // Current bind method is SIMPLE, check old password values, + // and new ones. + if (attr == 'agmtBootstrapBindPW') { + // Modifying password + if (value == "") { + all_good = false; + errObj['agmtBootstrapBindPW'] = true; + } else if (value != this.state.agmtBootstrapBindPWConfirm) { + modal_msg = "Bootstrap Passwords Do Not Match"; + errObj['agmtBootstrapBindPW'] = true; + errObj['agmtBootstrapBindPWConfirm'] = true; + all_good = false; + } else { + errObj['agmtBootstrapBindPW'] = false; + errObj['agmtBootstrapBindPWConfirm'] = false; + } + } else if (this.state.agmtBootstrapBindPW == "") { + // Current value is no good + all_good = false; + } + if (attr == 'agmtBootstrapBindPWConfirm') { + // Modifying password confirmation + if (value == "") { + all_good = false; + errObj['agmtBootstrapBindPWConfirm'] = true; + } else if (value != this.state.agmtBootstrapBindPW) { + modal_msg = "Bootstrap Passwords Do Not Match"; + errObj['agmtBootstrapBindPW'] = true; + errObj['agmtBootstrapBindPWConfirm'] = true; + all_good = false; + } else { + errObj['agmtBootstrapBindPW'] = false; + errObj['agmtBootstrapBindPWConfirm'] = false; + } + } else if (this.state.agmtBootstrapBindPWConfirm == "") { + // Current value is no good + all_good = false; + } + } else { + // Bind method is SSLCLIENTAUTH, make sure the connection protocol is LDAPS + if (this.state.agmtBootstrapProtocol != "LDAPS") { + modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH"; + errObj['agmtBootstrapProtocol'] = true; + all_good = false; + } else { + // All good, reset the errObj + errObj['agmtBootstrapProtocol'] = false; + errObj['agmtBootstrapBindMethod'] = false; + } + } + } + + if (attr == 'agmtSync') { if (!value) { if (this.state.agmtStartTime >= this.state.agmtEndTime) { modal_schedule_msg = "Schedule start time is greater than or equal to the end time"; @@ -256,7 +456,7 @@ export class ReplAgmts extends React.Component { let days = ["agmtSyncSun", "agmtSyncMon", "agmtSyncTue", "agmtSyncWed", "agmtSyncThu", "agmtSyncFri", "agmtSyncSat"]; for (let day of days) { - if ((e.target.id != day && this.state[day]) || (e.target.id == day && value)) { + if ((attr != day && this.state[day]) || (attr == day && value)) { have_days = true; break; } @@ -264,7 +464,7 @@ export class ReplAgmts extends React.Component { if (!have_days) { modal_schedule_msg = "You must select at least one day for replication"; all_good = false; - } else if (e.target.id == 'agmtStartTime') { + } else if (attr == 'agmtStartTime') { if (time_val == "") { all_good = false; errObj['agmtStartTime'] = true; @@ -279,7 +479,7 @@ export class ReplAgmts extends React.Component { errObj['agmtStartTime'] = false; errObj['agmtEndTime'] = false; } - } else if (e.target.id == 'agmtEndTime') { + } else if (attr == 'agmtEndTime') { if (time_val == "") { errObj['agmtEndTime'] = true; all_good = false; @@ -301,8 +501,9 @@ export class ReplAgmts extends React.Component { } // End of agmt modal live validation } + this.setState({ - [e.target.id]: value, + [attr]: value, errObj: errObj, agmtSaveOK: all_good, modalMsg: modal_msg, @@ -433,6 +634,12 @@ export class ReplAgmts extends React.Component { agmtBindDN: "", agmtBindPW: "", agmtBindPWConfirm: "", + agmtBootstrap: false, + agmtBootstrapProtocol: "LDAP", + agmtBootstrapBindMethod: "SIMPLE", + agmtBootstrapBindDN: "", + agmtBootstrapBindPW: "", + agmtBootstrapBindPWConfirm: "", agmtStripAttrs: [], agmtFracAttrs: [], agmtFracInitAttrs: [], @@ -495,6 +702,12 @@ export class ReplAgmts extends React.Component { let agmtBindDN = ""; let agmtBindPW = ""; let agmtBindPWConfirm = ""; + let agmtBootstrap = false; + let agmtBootstrapProtocol = ""; + let agmtBootstrapBindMethod = ""; + let agmtBootstrapBindDN = ""; + let agmtBootstrapBindPW = ""; + let agmtBootstrapBindPWConfirm = ""; let agmtStripAttrs = []; let agmtFracAttrs = []; let agmtFracInitAttrs = []; @@ -532,6 +745,20 @@ export class ReplAgmts extends React.Component { agmtBindPW = val; agmtBindPWConfirm = val; } + if (attr == "nsds5replicabootstraptransportinfo") { + agmtBootstrapProtocol = val; + } + if (attr == "nsds5replicabootstrapbindmethod") { + agmtBootstrapBindMethod = val.toUpperCase(); + } + if (attr == "nsds5replicabootstrapbinddn") { + agmtBootstrapBindDN = val; + agmtBootstrap = true; + } + if (attr == "nsds5replicabootstrapcredentials") { + agmtBootstrapBindPW = val; + agmtBootstrapBindPWConfirm = val; + } if (attr == "nsds5replicatedattributelist") { let attrs = val.replace("(objectclass=*) $ EXCLUDE", "").trim(); agmtFracAttrs = attrs.split(' '); @@ -589,6 +816,12 @@ export class ReplAgmts extends React.Component { agmtBindDN: agmtBindDN, agmtBindPW: agmtBindPW, agmtBindPWConfirm: agmtBindPWConfirm, + agmtBootstrap: agmtBootstrap, + agmtBootstrapProtocol: agmtBootstrapProtocol, + agmtBootstrapBindMethod: agmtBootstrapBindMethod, + agmtBootstrapBindDN: agmtBootstrapBindDN, + agmtBootstrapBindPW: agmtBootstrapBindPW, + agmtBootstrapBindPWConfirm: agmtBootstrapBindPWConfirm, agmtStripAttrs: agmtStripAttrs, agmtFracAttrs: agmtFracAttrs, agmtFracInitAttrs: agmtFracInitAttrs, @@ -603,6 +836,8 @@ export class ReplAgmts extends React.Component { agmtStartTime: agmtStartTime, agmtEndTime: agmtEndTime, agmtSaveOK: false, + modalMsg: "", + errObj: {}, // Record original values before editing _agmtName: agmtName, _agmtHost: agmtHost, @@ -612,6 +847,12 @@ export class ReplAgmts extends React.Component { _agmtBindDN: agmtBindDN, _agmtBindPW: agmtBindPW, _agmtBindPWConfirm: agmtBindPWConfirm, + _agmtBootstrap: agmtBootstrap, + _agmtBootstrapProtocol: agmtBootstrapProtocol, + _agmtBootstrapBindMethod: agmtBootstrapBindMethod, + _agmtBootstrapBindDN: agmtBootstrapBindDN, + _agmtBootstrapBindPW: agmtBootstrapBindPW, + _agmtBootstrapBindPWConfirm: agmtBootstrapBindPWConfirm, _agmtStripAttrs: agmtStripAttrs, _agmtFracAttrs: agmtFracAttrs, _agmtFracInitAttrs: agmtFracInitAttrs, @@ -699,6 +940,20 @@ export class ReplAgmts extends React.Component { if (this.state.agmtPort != this.state._agmtPort) { cmd.push('--port=' + this.state.agmtPort); } + if (this.state.agmtBootstrap) { + if (this.state.agmtBootstrapBindMethod != this.state._agmtBootstrapBindMethod) { + cmd.push('--bootstrap-bind-method=' + this.state.agmtBootstrapBindMethod); + } + if (this.state.agmtBootstrapProtocol != this.state._agmtBootstrapProtocol) { + cmd.push('--bootstrap-conn-protocol=' + this.state.agmtBootstrapProtocol); + } + if (this.state.agmtBootstrapBindPW != this.state._agmtBootstrapBindPW) { + cmd.push('--bootstrap-bind-passwd=' + this.state.agmtBootstrapBindPW); + } + if (this.state.agmtBootstrapBindDN != this.state._agmtBootstrapBindDN) { + cmd.push('--bootstrap-bind-dn=' + this.state.agmtBootstrapBindDN); + } + } this.setState({ savingAgmt: true @@ -933,6 +1188,21 @@ export class ReplAgmts extends React.Component { cmd.push('--strip-list=' + this.state.agmtStripAttrs.join(' ')); } + if (this.state.agmtBootstrap) { + if (this.state.agmtBootstrapBindDN != "") { + cmd.push('--bootstrap-bind-dn=' + this.state.agmtBootstrapBindDN); + } + if (this.state.agmtBootstrapBindDNPW != "") { + cmd.push('--bootstrap-bind-passwd=' + this.state.agmtBootstrapBindDNPW); + } + if (this.state.agmtBootstrapBindMethod != "") { + cmd.push('--bootstrap-bind-method=' + this.state.agmtBootstrapBindMethod); + } + if (this.state.agmtBootstrapProtocol != "") { + cmd.push('--bootstrap-conn-protocol=' + this.state.agmtBootstrapProtocol); + } + } + this.setState({ savingAgmt: true }); @@ -1030,6 +1300,12 @@ export class ReplAgmts extends React.Component { agmtBindPWConfirm={this.state.agmtBindPWConfirm} agmtProtocol={this.state.agmtProtocol} agmtBindMethod={this.state.agmtBindMethod} + agmtBootstrap={this.state.agmtBootstrap} + agmtBootstrapBindDN={this.state.agmtBootstrapBindDN} + agmtBootstrapBindPW={this.state.agmtBootstrapBindPW} + agmtBootstrapBindPWConfirm={this.state.agmtBootstrapBindPWConfirm} + agmtBootstrapProtocol={this.state.agmtBootstrapProtocol} + agmtBootstrapBindMethod={this.state.agmtBootstrapBindMethod} agmtStripAttrs={this.state.agmtStripAttrs} agmtFracAttrs={this.state.agmtFracAttrs} agmtFracInitAttrs={this.state.agmtFracInitAttrs} @@ -1066,6 +1342,12 @@ export class ReplAgmts extends React.Component { agmtBindPWConfirm={this.state.agmtBindPWConfirm} agmtProtocol={this.state.agmtProtocol} agmtBindMethod={this.state.agmtBindMethod} + agmtBootstrap={this.state.agmtBootstrap} + agmtBootstrapBindDN={this.state.agmtBootstrapBindDN} + agmtBootstrapBindPW={this.state.agmtBootstrapBindPW} + agmtBootstrapBindPWConfirm={this.state.agmtBootstrapBindPWConfirm} + agmtBootstrapProtocol={this.state.agmtBootstrapProtocol} + agmtBootstrapBindMethod={this.state.agmtBootstrapBindMethod} agmtStripAttrs={this.state.agmtStripAttrs} agmtFracAttrs={this.state.agmtFracAttrs} agmtFracInitAttrs={this.state.agmtFracInitAttrs} diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx b/src/cockpit/389-console/src/lib/replication/replModals.jsx index 0ef5d8b..17e56a7 100644 --- a/src/cockpit/389-console/src/lib/replication/replModals.jsx +++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx @@ -547,6 +547,12 @@ export class ReplAgmtModal extends React.Component { agmtBindDN, agmtBindPW, agmtBindPWConfirm, + agmtBootstrap, + agmtBootstrapBindDN, + agmtBootstrapBindPW, + agmtBootstrapBindPWConfirm, + agmtBootstrapProtocol, + agmtBootstrapBindMethod, agmtStripAttrs, agmtFracAttrs, agmtFracInitAttrs, @@ -572,6 +578,10 @@ export class ReplAgmtModal extends React.Component { let errMsgClass = "ds-center ds-modal-error"; let errMsg = errorMsg; let name = "agmt-modal"; + let bootstrapTitle = "If you are using Bind Group's on the consumer " + + "replica you can configure bootstrap credentials that can be used " + + "to do online initializations, or bootstrap a session if the bind " + + "groups get out of synchronization"; if (this.props.edit) { title = "Edit"; @@ -608,6 +618,91 @@ export class ReplAgmtModal extends React.Component { ; } + let bootstrapRow = +
+ + + Bind DN + + + + + + + + Password + + + + + + + + Confirm Password + + + + + + + + Connection Protocol + + + + + + + + Authentication Method + + + + + +
; + let scheduleRow =
@@ -731,13 +826,17 @@ export class ReplAgmtModal extends React.Component { className={error.agmtEndTime ? "ds-input-bad" : ""} onChange={handleChange} defaultValue={agmtEndTime} - /> + /> +
; if (agmtSync) { scheduleRow = ""; } + if (!agmtBootstrap) { + bootstrapRow = ""; + } return (
@@ -860,7 +959,12 @@ export class ReplAgmtModal extends React.Component { Connection Protocol - @@ -872,7 +976,12 @@ export class ReplAgmtModal extends React.Component { Authentication Method - @@ -887,7 +996,7 @@ export class ReplAgmtModal extends React.Component { textClosed="Show Advanced Settings" >
- + Exclude Attributes @@ -938,6 +1047,22 @@ export class ReplAgmtModal extends React.Component { /> +
+ + + + Configure Bootstrap Settings + + + + {bootstrapRow} +
Date: Aug 27 2020 18:39:38 +0000 Subject: [PATCH 4/4] Issue 51156 - RFE - add bootstrap credentials to repl agreement (upgrade update) Description: Add an upgrade function to add the new bootstrap password attribute to the AES reversible password plugin. relates: https://pagure.io/389-ds-base/issue/51156 Reviewed by: ? --- diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c index 47d608e..426c59a 100644 --- a/ldap/servers/slapd/upgrade.c +++ b/ldap/servers/slapd/upgrade.c @@ -42,6 +42,60 @@ upgrade_entry_exists_or_create(char *upgrade_id, char *filter, char *dn, char *e return uresult; } +/* + * Add the new replication bootstrap bind DN password attribute to the AES + * reversible password plugin + */ +static int32_t +upgrade_AES_reverpwd_plugin(void) +{ + Slapi_PBlock *search_pb = slapi_pblock_new(); + Slapi_Entry *plugin_entry = NULL; + Slapi_DN *sdn = NULL; + const char *plugin_dn = "cn=AES,cn=Password Storage Schemes,cn=plugins,cn=config"; + char *plugin_attr = "nsslapd-pluginarg2"; + char *repl_bootstrap_val = "nsds5replicabootstrapcredentials"; + upgrade_status uresult = UPGRADE_SUCCESS; + + sdn = slapi_sdn_new_dn_byref(plugin_dn); + slapi_search_get_entry(&search_pb, sdn, NULL, &plugin_entry, NULL); + if (plugin_entry) { + if (slapi_entry_attr_get_ref(plugin_entry, plugin_attr) == NULL) { + /* The attribute is not set, add it */ + Slapi_PBlock *mod_pb = slapi_pblock_new(); + LDAPMod mod_add; + LDAPMod *mods[2]; + char *add_val[2]; + int32_t result; + + add_val[0] = repl_bootstrap_val; + add_val[1] = 0; + mod_add.mod_op = LDAP_MOD_ADD; + mod_add.mod_type = plugin_attr; + mod_add.mod_values = add_val; + mods[0] = &mod_add; + mods[1] = 0; + + slapi_modify_internal_set_pb(mod_pb, plugin_dn, + mods, 0, 0, (void *)plugin_get_default_component_id(), 0); + slapi_modify_internal_pb(mod_pb); + slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + if (result != LDAP_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, "upgrade_AES_reverpwd_plugin", + "Failed to upgrade (%s) with new replication " + "bootstrap password attribute (%s), error %d\n", + plugin_dn, plugin_attr, result); + uresult = UPGRADE_FAILURE; + } + slapi_pblock_destroy(mod_pb); + } + } + slapi_search_get_entry_done(&search_pb); + slapi_sdn_free(&sdn); + + return uresult; +} + #ifdef RUST_ENABLE static upgrade_status upgrade_143_entryuuid_exists(void) { @@ -75,6 +129,10 @@ upgrade_server(void) { } #endif + if (upgrade_AES_reverpwd_plugin() != UPGRADE_SUCCESS) { + return UPGRADE_FAILURE; + } + return UPGRADE_SUCCESS; }