|
7 | 7 |
|
8 | 8 | package org.elasticsearch.xpack.remotecluster; |
9 | 9 |
|
| 10 | +import io.netty.handler.codec.http.HttpMethod; |
| 11 | + |
10 | 12 | import org.apache.http.HttpHost; |
11 | 13 | import org.apache.http.client.methods.HttpPost; |
12 | 14 | import org.elasticsearch.client.Request; |
|
25 | 27 | import org.elasticsearch.test.cluster.util.resource.Resource; |
26 | 28 | import org.elasticsearch.test.rest.ESRestTestCase; |
27 | 29 | import org.elasticsearch.test.rest.ObjectPath; |
| 30 | +import org.elasticsearch.xpack.security.SecurityFeatures; |
28 | 31 | import org.junit.AfterClass; |
29 | 32 | import org.junit.BeforeClass; |
30 | 33 |
|
|
33 | 36 | import java.nio.charset.StandardCharsets; |
34 | 37 | import java.util.Arrays; |
35 | 38 | import java.util.Base64; |
| 39 | +import java.util.List; |
36 | 40 | import java.util.Locale; |
37 | 41 | import java.util.Map; |
| 42 | +import java.util.Set; |
| 43 | +import java.util.stream.Collectors; |
38 | 44 |
|
39 | 45 | import static org.hamcrest.Matchers.anEmptyMap; |
40 | 46 | import static org.hamcrest.Matchers.equalTo; |
@@ -78,6 +84,7 @@ public abstract class AbstractRemoteClusterSecurityTestCase extends ESRestTestCa |
78 | 84 | .configFile("remote-cluster-client.key", Resource.fromClasspath("ssl/remote-cluster-client.key")) |
79 | 85 | .configFile("remote-cluster-client.crt", Resource.fromClasspath("ssl/remote-cluster-client.crt")) |
80 | 86 | .configFile("remote-cluster-client-ca.crt", Resource.fromClasspath("ssl/remote-cluster-client-ca.crt")) |
| 87 | + .configFile("signing.crt", Resource.fromClasspath("signing/signing.crt")) |
81 | 88 | .module("reindex") // Needed for the role metadata migration |
82 | 89 | .user(USER, PASS.toString()); |
83 | 90 |
|
@@ -197,8 +204,10 @@ protected void configureRemoteCluster( |
197 | 204 | boolean isProxyMode, |
198 | 205 | boolean skipUnavailable |
199 | 206 | ) throws Exception { |
| 207 | + putFulfillingClusterSettings(); |
| 208 | + |
200 | 209 | // For configurable remote cluster security, this method assumes the cross cluster access API key is already configured in keystore |
201 | | - putRemoteClusterSettings(clusterAlias, targetFulfillingCluster, basicSecurity, isProxyMode, skipUnavailable); |
| 210 | + putQueryClusterSettings(clusterAlias, targetFulfillingCluster, basicSecurity, isProxyMode, skipUnavailable); |
202 | 211 |
|
203 | 212 | // Ensure remote cluster is connected |
204 | 213 | checkRemoteConnection(clusterAlias, targetFulfillingCluster, basicSecurity, isProxyMode); |
@@ -234,7 +243,18 @@ protected void reloadSecureSettings() throws IOException { |
234 | 243 | } |
235 | 244 | } |
236 | 245 |
|
237 | | - protected void putRemoteClusterSettings( |
| 246 | + protected void putFulfillingClusterSettings() throws IOException { |
| 247 | + if (getFulfillingClusterNodeFeatures().contains(SecurityFeatures.CERTIFICATE_IDENTITY_FIELD_FEATURE.id())) { |
| 248 | + final var request = newXContentRequest(HttpMethod.PUT, "/_cluster/settings", (builder, params) -> { |
| 249 | + builder.startObject("persistent"); |
| 250 | + Settings.builder().put("cluster.remote.signing.certificate_authorities", "signing.crt").build().toXContent(builder, params); |
| 251 | + return builder.endObject(); |
| 252 | + }); |
| 253 | + assertOK(performRequestAgainstFulfillingCluster(request)); |
| 254 | + } |
| 255 | + } |
| 256 | + |
| 257 | + protected void putQueryClusterSettings( |
238 | 258 | String clusterAlias, |
239 | 259 | ElasticsearchCluster targetFulfillingCluster, |
240 | 260 | boolean basicSecurity, |
@@ -303,6 +323,22 @@ protected static String randomEncodedApiKey() { |
303 | 323 | .encodeToString((UUIDs.base64UUID() + ":" + UUIDs.randomBase64UUIDSecureString()).getBytes(StandardCharsets.UTF_8)); |
304 | 324 | } |
305 | 325 |
|
| 326 | + protected Set<String> getFulfillingClusterNodeFeatures() throws IOException { |
| 327 | + final Request request = new Request("GET", "_cluster/state"); |
| 328 | + request.addParameter("filter_path", "nodes_features"); |
| 329 | + final Response response = performRequestAgainstFulfillingCluster(request); |
| 330 | + |
| 331 | + var responseData = responseAsMap(response); |
| 332 | + if (responseData.get("nodes_features") instanceof List<?> nodesFeatures) { |
| 333 | + return nodesFeatures.stream().map(Map.class::cast).flatMap(nodeFeatureMap -> { |
| 334 | + @SuppressWarnings("unchecked") |
| 335 | + List<String> features = (List<String>) nodeFeatureMap.get("features"); |
| 336 | + return features.stream(); |
| 337 | + }).collect(Collectors.toSet()); |
| 338 | + } |
| 339 | + return Set.of(); |
| 340 | + } |
| 341 | + |
306 | 342 | protected record TestClusterConfigProviders(LocalClusterConfigProvider server, LocalClusterConfigProvider client) {} |
307 | 343 |
|
308 | 344 | protected static TestClusterConfigProviders EMPTY_CONFIG_PROVIDERS = new TestClusterConfigProviders(cluster -> {}, cluster -> {}); |
|
0 commit comments