Skip to content

Commit e126dc7

Browse files
authored
Add redacted cluster credentials to RemoteConnectionInfo response (#94527)
This PR adds a redacted cluster_credentials field to the RemoteConnctionInfo API response to differenitate between basic and RCS remote clusters. The new field is available only for RCS remote clusters.
1 parent 6d79675 commit e126dc7

File tree

7 files changed

+121
-24
lines changed

7 files changed

+121
-24
lines changed

qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public void initSearchClient() throws IOException {
176176
assertOK(response);
177177
ObjectPath responseObject = ObjectPath.createFromResponse(response);
178178
assertNotNull(responseObject.evaluate(REMOTE_CLUSTER_NAME));
179+
assertNull(responseObject.evaluate(REMOTE_CLUSTER_NAME + ".cluster_credentials"));
179180
logger.info("Established connection to remote cluster [" + REMOTE_CLUSTER_NAME + "]");
180181
}
181182

qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public void initSearchClient() throws IOException {
194194
assertOK(response);
195195
ObjectPath responseObject = ObjectPath.createFromResponse(response);
196196
assertNotNull(responseObject.evaluate(REMOTE_CLUSTER_NAME));
197+
assertEquals("::es_redacted::", responseObject.evaluate(REMOTE_CLUSTER_NAME + ".cluster_credentials"));
197198
logger.info("Established connection to remote cluster [" + REMOTE_CLUSTER_NAME + "]");
198199
}
199200

server/src/main/java/org/elasticsearch/transport/RemoteClusterConnection.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,13 @@ boolean isNodeConnected(final DiscoveryNode node) {
197197
* Get the information about remote nodes to be rendered on {@code _remote/info} requests.
198198
*/
199199
public RemoteConnectionInfo getConnectionInfo() {
200-
return new RemoteConnectionInfo(clusterAlias, connectionStrategy.getModeInfo(), initialConnectionTimeout, skipUnavailable);
200+
return new RemoteConnectionInfo(
201+
clusterAlias,
202+
connectionStrategy.getModeInfo(),
203+
initialConnectionTimeout,
204+
skipUnavailable,
205+
REMOTE_CLUSTER_PROFILE.equals(remoteConnectionManager.getConnectionProfile().getTransportProfile())
206+
);
201207
}
202208

203209
int getNumNodesConnected() {

server/src/main/java/org/elasticsearch/transport/RemoteClusterPortSettings.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.elasticsearch.transport;
1010

11+
import org.elasticsearch.TransportVersion;
1112
import org.elasticsearch.common.settings.Setting;
1213
import org.elasticsearch.common.settings.Settings;
1314
import org.elasticsearch.common.unit.ByteSizeValue;
@@ -35,6 +36,9 @@
3536
* Contains the settings and some associated logic for the settings related to the Remote Access port, used by Remote Cluster Security 2.0.
3637
*/
3738
public class RemoteClusterPortSettings {
39+
40+
public static final TransportVersion TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY = TransportVersion.V_8_8_0;
41+
3842
public static final String REMOTE_CLUSTER_PROFILE = "_remote_cluster";
3943
public static final String REMOTE_CLUSTER_PREFIX = "remote_cluster.";
4044

server/src/main/java/org/elasticsearch/transport/RemoteConnectionInfo.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.List;
2222
import java.util.Objects;
2323

24+
import static org.elasticsearch.transport.RemoteClusterPortSettings.TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY;
25+
2426
/**
2527
* This class encapsulates all remote cluster information to be rendered on
2628
* {@code _remote/info} requests.
@@ -31,12 +33,20 @@ public final class RemoteConnectionInfo implements ToXContentFragment, Writeable
3133
final TimeValue initialConnectionTimeout;
3234
final String clusterAlias;
3335
final boolean skipUnavailable;
34-
35-
public RemoteConnectionInfo(String clusterAlias, ModeInfo modeInfo, TimeValue initialConnectionTimeout, boolean skipUnavailable) {
36+
final boolean hasClusterCredentials;
37+
38+
public RemoteConnectionInfo(
39+
String clusterAlias,
40+
ModeInfo modeInfo,
41+
TimeValue initialConnectionTimeout,
42+
boolean skipUnavailable,
43+
boolean hasClusterCredentials
44+
) {
3645
this.clusterAlias = clusterAlias;
3746
this.modeInfo = modeInfo;
3847
this.initialConnectionTimeout = initialConnectionTimeout;
3948
this.skipUnavailable = skipUnavailable;
49+
this.hasClusterCredentials = hasClusterCredentials;
4050
}
4151

4252
public RemoteConnectionInfo(StreamInput input) throws IOException {
@@ -46,6 +56,11 @@ public RemoteConnectionInfo(StreamInput input) throws IOException {
4656
initialConnectionTimeout = input.readTimeValue();
4757
clusterAlias = input.readString();
4858
skipUnavailable = input.readBoolean();
59+
if (input.getTransportVersion().onOrAfter(TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY)) {
60+
hasClusterCredentials = input.readBoolean();
61+
} else {
62+
hasClusterCredentials = false;
63+
}
4964
} else {
5065
List<String> seedNodes = Arrays.asList(input.readStringArray());
5166
int connectionsPerCluster = input.readVInt();
@@ -54,6 +69,7 @@ public RemoteConnectionInfo(StreamInput input) throws IOException {
5469
clusterAlias = input.readString();
5570
skipUnavailable = input.readBoolean();
5671
modeInfo = new SniffConnectionStrategy.SniffModeInfo(seedNodes, connectionsPerCluster, numNodesConnected);
72+
hasClusterCredentials = false;
5773
}
5874
}
5975

@@ -99,6 +115,9 @@ public void writeTo(StreamOutput out) throws IOException {
99115
}
100116
out.writeString(clusterAlias);
101117
out.writeBoolean(skipUnavailable);
118+
if (out.getTransportVersion().onOrAfter(TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY)) {
119+
out.writeBoolean(hasClusterCredentials);
120+
}
102121
}
103122

104123
@Override
@@ -110,6 +129,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
110129
modeInfo.toXContent(builder, params);
111130
builder.field("initial_connect_timeout", initialConnectionTimeout);
112131
builder.field("skip_unavailable", skipUnavailable);
132+
if (hasClusterCredentials) {
133+
builder.field("cluster_credentials", "::es_redacted::");
134+
}
113135
}
114136
builder.endObject();
115137
return builder;
@@ -123,12 +145,13 @@ public boolean equals(Object o) {
123145
return skipUnavailable == that.skipUnavailable
124146
&& Objects.equals(modeInfo, that.modeInfo)
125147
&& Objects.equals(initialConnectionTimeout, that.initialConnectionTimeout)
126-
&& Objects.equals(clusterAlias, that.clusterAlias);
148+
&& Objects.equals(clusterAlias, that.clusterAlias)
149+
&& hasClusterCredentials == that.hasClusterCredentials;
127150
}
128151

129152
@Override
130153
public int hashCode() {
131-
return Objects.hash(modeInfo, initialConnectionTimeout, clusterAlias, skipUnavailable);
154+
return Objects.hash(modeInfo, initialConnectionTimeout, clusterAlias, skipUnavailable, hasClusterCredentials);
132155
}
133156

134157
public interface ModeInfo extends ToXContentFragment, Writeable {

server/src/test/java/org/elasticsearch/transport/RemoteClusterConnectionTests.java

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -374,18 +374,58 @@ public void run() {
374374
}
375375

376376
public void testGetConnectionInfo() throws Exception {
377+
doTestGetConnectionInfo(false);
378+
doTestGetConnectionInfo(true);
379+
}
380+
381+
private void doTestGetConnectionInfo(boolean hasClusterCredentials) throws Exception {
377382
List<DiscoveryNode> knownNodes = new CopyOnWriteArrayList<>();
383+
final Settings seedTransportSettings;
384+
if (hasClusterCredentials) {
385+
seedTransportSettings = Settings.builder()
386+
.put(RemoteClusterPortSettings.REMOTE_CLUSTER_SERVER_ENABLED.getKey(), "true")
387+
.put(RemoteClusterPortSettings.PORT.getKey(), "0")
388+
.build();
389+
} else {
390+
seedTransportSettings = Settings.EMPTY;
391+
}
378392
try (
379-
MockTransportService transport1 = startTransport("seed_node", knownNodes, Version.CURRENT, TransportVersion.CURRENT);
380-
MockTransportService transport2 = startTransport("seed_node_1", knownNodes, Version.CURRENT, TransportVersion.CURRENT);
381-
MockTransportService transport3 = startTransport("discoverable_node", knownNodes, Version.CURRENT, TransportVersion.CURRENT)
393+
MockTransportService transport1 = startTransport(
394+
"seed_node",
395+
knownNodes,
396+
Version.CURRENT,
397+
TransportVersion.CURRENT,
398+
threadPool,
399+
seedTransportSettings
400+
);
401+
MockTransportService transport2 = startTransport(
402+
"seed_node_1",
403+
knownNodes,
404+
Version.CURRENT,
405+
TransportVersion.CURRENT,
406+
threadPool,
407+
seedTransportSettings
408+
);
409+
MockTransportService transport3 = startTransport(
410+
"discoverable_node",
411+
knownNodes,
412+
Version.CURRENT,
413+
TransportVersion.CURRENT,
414+
threadPool,
415+
seedTransportSettings
416+
)
382417
) {
383418
DiscoveryNode node1 = transport1.getLocalDiscoNode();
384419
DiscoveryNode node2 = transport3.getLocalDiscoNode();
385420
DiscoveryNode node3 = transport2.getLocalDiscoNode();
386-
knownNodes.add(transport1.getLocalDiscoNode());
387-
knownNodes.add(transport3.getLocalDiscoNode());
388-
knownNodes.add(transport2.getLocalDiscoNode());
421+
if (hasClusterCredentials) {
422+
node1 = node1.withTransportAddress(transport1.boundRemoteAccessAddress().publishAddress());
423+
node2 = node2.withTransportAddress(transport3.boundRemoteAccessAddress().publishAddress());
424+
node3 = node3.withTransportAddress(transport2.boundRemoteAccessAddress().publishAddress());
425+
}
426+
knownNodes.add(node1);
427+
knownNodes.add(node2);
428+
knownNodes.add(node3);
389429
Collections.shuffle(knownNodes, random());
390430
List<String> seedNodes = addresses(node3, node1, node2);
391431
Collections.shuffle(seedNodes, random());
@@ -407,6 +447,15 @@ public void testGetConnectionInfo() throws Exception {
407447
.put(buildSniffSettings(clusterAlias, seedNodes))
408448
.put(SniffConnectionStrategy.REMOTE_CONNECTIONS_PER_CLUSTER.getKey(), maxNumConnections)
409449
.build();
450+
if (hasClusterCredentials) {
451+
settings = Settings.builder()
452+
.put(settings)
453+
.put(
454+
RemoteClusterService.REMOTE_CLUSTER_AUTHORIZATION.getConcreteSettingForNamespace(clusterAlias).getKey(),
455+
randomAlphaOfLength(20)
456+
)
457+
.build();
458+
}
410459
try (RemoteClusterConnection connection = new RemoteClusterConnection(settings, clusterAlias, service)) {
411460
// test no nodes connected
412461
RemoteConnectionInfo remoteConnectionInfo = assertSerialization(connection.getConnectionInfo());
@@ -416,6 +465,7 @@ public void testGetConnectionInfo() throws Exception {
416465
assertEquals(3, sniffInfo.seedNodes.size());
417466
assertEquals(maxNumConnections, sniffInfo.maxConnectionsPerCluster);
418467
assertEquals(clusterAlias, remoteConnectionInfo.clusterAlias);
468+
assertEquals(hasClusterCredentials, remoteConnectionInfo.hasClusterCredentials);
419469
}
420470
}
421471
}
@@ -436,22 +486,26 @@ public void testRemoteConnectionInfo() throws IOException {
436486
modeInfo2 = new ProxyConnectionStrategy.ProxyModeInfo(remoteAddresses.get(0), serverName, 18, 17);
437487
}
438488

439-
RemoteConnectionInfo stats = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(30), false);
489+
RemoteConnectionInfo stats = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(30), false, false);
440490
assertSerialization(stats);
441491

442-
RemoteConnectionInfo stats1 = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(30), true);
492+
RemoteConnectionInfo stats1 = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(30), true, false);
443493
assertSerialization(stats1);
444494
assertNotEquals(stats, stats1);
445495

446-
stats1 = new RemoteConnectionInfo("test_cluster_1", modeInfo1, TimeValue.timeValueMinutes(30), false);
496+
stats1 = new RemoteConnectionInfo("test_cluster_1", modeInfo1, TimeValue.timeValueMinutes(30), false, false);
447497
assertSerialization(stats1);
448498
assertNotEquals(stats, stats1);
449499

450-
stats1 = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(325), false);
500+
stats1 = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(325), false, false);
451501
assertSerialization(stats1);
452502
assertNotEquals(stats, stats1);
453503

454-
stats1 = new RemoteConnectionInfo("test_cluster", modeInfo2, TimeValue.timeValueMinutes(30), false);
504+
stats1 = new RemoteConnectionInfo("test_cluster", modeInfo2, TimeValue.timeValueMinutes(30), false, false);
505+
assertSerialization(stats1);
506+
assertNotEquals(stats, stats1);
507+
508+
stats1 = new RemoteConnectionInfo("test_cluster", modeInfo1, TimeValue.timeValueMinutes(30), false, true);
455509
assertSerialization(stats1);
456510
assertNotEquals(stats, stats1);
457511
}
@@ -481,16 +535,23 @@ public void testRenderConnectionInfoXContent() throws IOException {
481535
} else {
482536
modeInfo = new ProxyConnectionStrategy.ProxyModeInfo(remoteAddresses.get(0), serverName, 18, 16);
483537
}
484-
485-
RemoteConnectionInfo stats = new RemoteConnectionInfo("test_cluster", modeInfo, TimeValue.timeValueMinutes(30), true);
538+
final boolean hasClusterCredentials = randomBoolean();
539+
540+
RemoteConnectionInfo stats = new RemoteConnectionInfo(
541+
"test_cluster",
542+
modeInfo,
543+
TimeValue.timeValueMinutes(30),
544+
true,
545+
hasClusterCredentials
546+
);
486547
stats = assertSerialization(stats);
487548
XContentBuilder builder = XContentFactory.jsonBuilder();
488549
builder.startObject();
489550
stats.toXContent(builder, null);
490551
builder.endObject();
491552

492553
if (sniff) {
493-
assertEquals(XContentHelper.stripWhitespace("""
554+
assertEquals(XContentHelper.stripWhitespace(Strings.format("""
494555
{
495556
"test_cluster": {
496557
"connected": true,
@@ -499,11 +560,11 @@ public void testRenderConnectionInfoXContent() throws IOException {
499560
"num_nodes_connected": 2,
500561
"max_connections_per_cluster": 3,
501562
"initial_connect_timeout": "30m",
502-
"skip_unavailable": true
563+
"skip_unavailable": true%s
503564
}
504-
}"""), Strings.toString(builder));
565+
}""", hasClusterCredentials ? ",\"cluster_credentials\":\"::es_redacted::\"" : "")), Strings.toString(builder));
505566
} else {
506-
assertEquals(XContentHelper.stripWhitespace("""
567+
assertEquals(XContentHelper.stripWhitespace(Strings.format("""
507568
{
508569
"test_cluster": {
509570
"connected": true,
@@ -513,9 +574,9 @@ public void testRenderConnectionInfoXContent() throws IOException {
513574
"num_proxy_sockets_connected": 16,
514575
"max_proxy_socket_connections": 18,
515576
"initial_connect_timeout": "30m",
516-
"skip_unavailable": true
577+
"skip_unavailable": true%s
517578
}
518-
}"""), Strings.toString(builder));
579+
}""", hasClusterCredentials ? ",\"cluster_credentials\":\"::es_redacted::\"" : "")), Strings.toString(builder));
519580
}
520581
}
521582

x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/AbstractRemoteClusterSecurityTestCase.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ protected String configureRemoteClustersWithApiKey(String indicesPrivilegesJson,
128128
if (false == isProxyMode) {
129129
assertThat(ObjectPath.eval("my_remote_cluster.num_nodes_connected", remoteInfoMap), equalTo(numberOfFcNodes));
130130
}
131+
assertThat(ObjectPath.eval("my_remote_cluster.cluster_credentials", remoteInfoMap), equalTo("::es_redacted::"));
131132
});
132133

133134
return (String) apiKeyMap.get("id");

0 commit comments

Comments
 (0)