Skip to content

Commit b3962c1

Browse files
committed
[CCR] Make auto follow patterns work with security (#33501)
Relates to #33007
1 parent 8f3246d commit b3962c1

File tree

12 files changed

+257
-111
lines changed

12 files changed

+257
-111
lines changed

x-pack/plugin/ccr/qa/multi-cluster-with-security/roles.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ccruser:
22
cluster:
33
- manage_ccr
44
indices:
5-
- names: [ 'allowed-index' ]
5+
- names: [ 'allowed-index', 'logs-eu-*' ]
66
privileges:
77
- monitor
88
- read

x-pack/plugin/ccr/qa/multi-cluster-with-security/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexSecurityIT.java

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package org.elasticsearch.xpack.ccr;
77

8+
import org.apache.http.HttpHost;
89
import org.apache.http.util.EntityUtils;
910
import org.elasticsearch.client.Request;
1011
import org.elasticsearch.client.Response;
@@ -119,6 +120,45 @@ public void testFollowIndex() throws Exception {
119120
}
120121
}
121122

123+
public void testAutoFollowPatterns() throws Exception {
124+
assumeFalse("Test should only run when both clusters are running", runningAgainstLeaderCluster);
125+
String allowedIndex = "logs-eu-20190101";
126+
String disallowedIndex = "logs-us-20190101";
127+
128+
Request request = new Request("PUT", "/_ccr/auto_follow/leader_cluster");
129+
request.setJsonEntity("{\"leader_index_patterns\": [\"logs-*\"]}");
130+
assertOK(client().performRequest(request));
131+
132+
try (RestClient leaderClient = buildLeaderClient()) {
133+
for (String index : new String[]{allowedIndex, disallowedIndex}) {
134+
Settings settings = Settings.builder()
135+
.put("index.soft_deletes.enabled", true)
136+
.build();
137+
String requestBody = "{\"settings\": " + Strings.toString(settings) +
138+
", \"mappings\": {\"_doc\": {\"properties\": {\"field\": {\"type\": \"keyword\"}}}} }";
139+
request = new Request("PUT", "/" + index);
140+
request.setJsonEntity(requestBody);
141+
assertOK(leaderClient.performRequest(request));
142+
143+
for (int i = 0; i < 5; i++) {
144+
String id = Integer.toString(i);
145+
index(leaderClient, index, id, "field", i, "filtered_field", "true");
146+
}
147+
}
148+
}
149+
150+
assertBusy(() -> {
151+
ensureYellow(allowedIndex);
152+
verifyDocuments(adminClient(), allowedIndex, 5);
153+
});
154+
assertThat(indexExists(adminClient(), disallowedIndex), is(false));
155+
156+
// Cleanup by deleting auto follow pattern and unfollowing:
157+
request = new Request("DELETE", "/_ccr/auto_follow/leader_cluster");
158+
assertOK(client().performRequest(request));
159+
unfollowIndex(allowedIndex);
160+
}
161+
122162
private int countCcrNodeTasks() throws IOException {
123163
final Request request = new Request("GET", "/_tasks");
124164
request.addParameter("detailed", "true");
@@ -139,14 +179,18 @@ private int countCcrNodeTasks() throws IOException {
139179
}
140180

141181
private static void index(String index, String id, Object... fields) throws IOException {
182+
index(adminClient(), index, id, fields);
183+
}
184+
185+
private static void index(RestClient client, String index, String id, Object... fields) throws IOException {
142186
XContentBuilder document = jsonBuilder().startObject();
143187
for (int i = 0; i < fields.length; i += 2) {
144188
document.field((String) fields[i], fields[i + 1]);
145189
}
146190
document.endObject();
147191
final Request request = new Request("POST", "/" + index + "/_doc/" + id);
148192
request.setJsonEntity(Strings.toString(document));
149-
assertOK(adminClient().performRequest(request));
193+
assertOK(client.performRequest(request));
150194
}
151195

152196
private static void refresh(String index) throws IOException {
@@ -201,11 +245,34 @@ protected static void createIndex(String name, Settings settings, String mapping
201245
assertOK(adminClient().performRequest(request));
202246
}
203247

248+
private static void ensureYellow(String index) throws IOException {
249+
Request request = new Request("GET", "/_cluster/health/" + index);
250+
request.addParameter("wait_for_status", "yellow");
251+
request.addParameter("wait_for_no_relocating_shards", "true");
252+
request.addParameter("wait_for_no_initializing_shards", "true");
253+
request.addParameter("timeout", "70s");
254+
request.addParameter("level", "shards");
255+
adminClient().performRequest(request);
256+
}
257+
258+
private RestClient buildLeaderClient() throws IOException {
259+
assert runningAgainstLeaderCluster == false;
260+
String leaderUrl = System.getProperty("tests.leader_host");
261+
int portSeparator = leaderUrl.lastIndexOf(':');
262+
HttpHost httpHost = new HttpHost(leaderUrl.substring(0, portSeparator),
263+
Integer.parseInt(leaderUrl.substring(portSeparator + 1)), getProtocol());
264+
return buildClient(restAdminSettings(), new HttpHost[]{httpHost});
265+
}
266+
204267
private static boolean indexExists(RestClient client, String index) throws IOException {
205268
Response response = client.performRequest(new Request("HEAD", "/" + index));
206269
return RestStatus.OK.getStatus() == response.getStatusLine().getStatusCode();
207270
}
208271

272+
private static void unfollowIndex(String followIndex) throws IOException {
273+
assertOK(client().performRequest(new Request("POST", "/" + followIndex + "/_ccr/unfollow")));
274+
}
275+
209276
private static void verifyCcrMonitoring(String expectedLeaderIndex, String expectedFollowerIndex) throws IOException {
210277
ensureYellow(".monitoring-*");
211278

@@ -239,14 +306,4 @@ private static void verifyCcrMonitoring(String expectedLeaderIndex, String expec
239306
assertThat(numberOfOperationsIndexed, greaterThanOrEqualTo(1));
240307
}
241308

242-
private static void ensureYellow(String index) throws IOException {
243-
Request request = new Request("GET", "/_cluster/health/" + index);
244-
request.addParameter("wait_for_status", "yellow");
245-
request.addParameter("wait_for_no_relocating_shards", "true");
246-
request.addParameter("wait_for_no_initializing_shards", "true");
247-
request.addParameter("timeout", "70s");
248-
request.addParameter("level", "shards");
249-
adminClient().performRequest(request);
250-
}
251-
252309
}

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/CcrLicenseChecker.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,44 @@
77
package org.elasticsearch.xpack.ccr;
88

99
import org.elasticsearch.ElasticsearchStatusException;
10+
import org.elasticsearch.action.Action;
1011
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.action.ActionRequest;
13+
import org.elasticsearch.action.ActionRequestBuilder;
14+
import org.elasticsearch.action.ActionResponse;
1115
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
1216
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
17+
import org.elasticsearch.action.support.ContextPreservingActionListener;
1318
import org.elasticsearch.action.admin.indices.stats.IndexShardStats;
1419
import org.elasticsearch.action.admin.indices.stats.IndexStats;
1520
import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
1621
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
1722
import org.elasticsearch.action.admin.indices.stats.ShardStats;
1823
import org.elasticsearch.client.Client;
24+
import org.elasticsearch.client.FilterClient;
1925
import org.elasticsearch.cluster.ClusterState;
2026
import org.elasticsearch.cluster.metadata.IndexMetaData;
27+
import org.elasticsearch.common.util.concurrent.ThreadContext;
2128
import org.elasticsearch.common.CheckedConsumer;
2229
import org.elasticsearch.index.engine.CommitStats;
2330
import org.elasticsearch.index.engine.Engine;
2431
import org.elasticsearch.index.shard.ShardId;
2532
import org.elasticsearch.license.RemoteClusterLicenseChecker;
2633
import org.elasticsearch.license.XPackLicenseState;
2734
import org.elasticsearch.rest.RestStatus;
35+
import org.elasticsearch.xpack.ccr.action.ShardFollowTask;
2836
import org.elasticsearch.xpack.core.XPackPlugin;
2937

3038
import java.util.Collections;
3139
import java.util.Locale;
40+
import java.util.Map;
3241
import java.util.Objects;
3342
import java.util.function.BiConsumer;
3443
import java.util.function.BooleanSupplier;
3544
import java.util.function.Consumer;
3645
import java.util.function.Function;
46+
import java.util.function.Supplier;
47+
import java.util.stream.Collectors;
3748

3849
/**
3950
* Encapsulates licensing checking for CCR.
@@ -93,6 +104,7 @@ public <T> void checkRemoteClusterLicenseAndFetchLeaderIndexMetadataAndHistoryUU
93104
request.indices(leaderIndex);
94105
checkRemoteClusterLicenseAndFetchClusterState(
95106
client,
107+
Collections.emptyMap(),
96108
clusterAlias,
97109
request,
98110
onFailure,
@@ -115,19 +127,22 @@ public <T> void checkRemoteClusterLicenseAndFetchLeaderIndexMetadataAndHistoryUU
115127
*
116128
* @param client the client
117129
* @param clusterAlias the remote cluster alias
130+
* @param headers the headers to use for leader client
118131
* @param request the cluster state request
119132
* @param onFailure the failure consumer
120133
* @param leaderClusterStateConsumer the leader cluster state consumer
121134
* @param <T> the type of response the listener is waiting for
122135
*/
123136
public <T> void checkRemoteClusterLicenseAndFetchClusterState(
124137
final Client client,
138+
final Map<String, String> headers,
125139
final String clusterAlias,
126140
final ClusterStateRequest request,
127141
final Consumer<Exception> onFailure,
128142
final Consumer<ClusterState> leaderClusterStateConsumer) {
129143
checkRemoteClusterLicenseAndFetchClusterState(
130144
client,
145+
headers,
131146
clusterAlias,
132147
request,
133148
onFailure,
@@ -144,6 +159,7 @@ public <T> void checkRemoteClusterLicenseAndFetchClusterState(
144159
*
145160
* @param client the client
146161
* @param clusterAlias the remote cluster alias
162+
* @param headers the headers to use for leader client
147163
* @param request the cluster state request
148164
* @param onFailure the failure consumer
149165
* @param leaderClusterStateConsumer the leader cluster state consumer
@@ -153,6 +169,7 @@ public <T> void checkRemoteClusterLicenseAndFetchClusterState(
153169
*/
154170
private <T> void checkRemoteClusterLicenseAndFetchClusterState(
155171
final Client client,
172+
final Map<String, String> headers,
156173
final String clusterAlias,
157174
final ClusterStateRequest request,
158175
final Consumer<Exception> onFailure,
@@ -167,7 +184,7 @@ private <T> void checkRemoteClusterLicenseAndFetchClusterState(
167184
@Override
168185
public void onResponse(final RemoteClusterLicenseChecker.LicenseCheck licenseCheck) {
169186
if (licenseCheck.isSuccess()) {
170-
final Client leaderClient = client.getRemoteClusterClient(clusterAlias);
187+
final Client leaderClient = wrapClient(client.getRemoteClusterClient(clusterAlias), headers);
171188
final ActionListener<ClusterStateResponse> clusterStateListener =
172189
ActionListener.wrap(s -> leaderClusterStateConsumer.accept(s.getState()), onFailure);
173190
// following an index in remote cluster, so use remote client to fetch leader index metadata
@@ -237,6 +254,34 @@ public void fetchLeaderHistoryUUIDs(
237254
leaderClient.admin().indices().stats(request, ActionListener.wrap(indicesStatsHandler, onFailure));
238255
}
239256

257+
public static Client wrapClient(Client client, Map<String, String> headers) {
258+
if (headers.isEmpty()) {
259+
return client;
260+
} else {
261+
final ThreadContext threadContext = client.threadPool().getThreadContext();
262+
Map<String, String> filteredHeaders = headers.entrySet().stream()
263+
.filter(e -> ShardFollowTask.HEADER_FILTERS.contains(e.getKey()))
264+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
265+
return new FilterClient(client) {
266+
@Override
267+
protected <Request extends ActionRequest, Response extends ActionResponse,
268+
RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>>
269+
void doExecute(Action<Request, Response, RequestBuilder> action, Request request, ActionListener<Response> listener) {
270+
final Supplier<ThreadContext.StoredContext> supplier = threadContext.newRestorableContext(false);
271+
try (ThreadContext.StoredContext ignore = stashWithHeaders(threadContext, filteredHeaders)) {
272+
super.doExecute(action, request, new ContextPreservingActionListener<>(supplier, listener));
273+
}
274+
}
275+
};
276+
}
277+
}
278+
279+
private static ThreadContext.StoredContext stashWithHeaders(ThreadContext threadContext, Map<String, String> headers) {
280+
final ThreadContext.StoredContext storedContext = threadContext.stashContext();
281+
threadContext.copyHeaders(headers.entrySet());
282+
return storedContext;
283+
}
284+
240285
private static ElasticsearchStatusException indexMetadataNonCompliantRemoteLicense(
241286
final String leaderIndex, final RemoteClusterLicenseChecker.LicenseCheck licenseCheck) {
242287
final String clusterAlias = licenseCheck.remoteClusterLicenseInfo().clusterAlias();

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,22 @@ private void doAutoFollow() {
103103
AutoFollower operation = new AutoFollower(handler, followerClusterState) {
104104

105105
@Override
106-
void getLeaderClusterState(final String leaderClusterAlias, final BiConsumer<ClusterState, Exception> handler) {
106+
void getLeaderClusterState(final Map<String, String> headers,
107+
final String leaderClusterAlias,
108+
final BiConsumer<ClusterState, Exception> handler) {
107109
final ClusterStateRequest request = new ClusterStateRequest();
108110
request.clear();
109111
request.metaData(true);
110112

111113
if ("_local_".equals(leaderClusterAlias)) {
114+
Client client = CcrLicenseChecker.wrapClient(AutoFollowCoordinator.this.client, headers);
112115
client.admin().cluster().state(
113116
request, ActionListener.wrap(r -> handler.accept(r.getState(), null), e -> handler.accept(null, e)));
114117
} else {
115-
final Client leaderClient = client.getRemoteClusterClient(leaderClusterAlias);
116118
// TODO: set non-compliant status on auto-follow coordination that can be viewed via a stats API
117119
ccrLicenseChecker.checkRemoteClusterLicenseAndFetchClusterState(
118-
leaderClient,
120+
client,
121+
headers,
119122
leaderClusterAlias,
120123
request,
121124
e -> handler.accept(null, e),
@@ -125,15 +128,22 @@ void getLeaderClusterState(final String leaderClusterAlias, final BiConsumer<Clu
125128
}
126129

127130
@Override
128-
void createAndFollow(FollowIndexAction.Request followRequest,
131+
void createAndFollow(Map<String, String> headers,
132+
FollowIndexAction.Request followRequest,
129133
Runnable successHandler,
130134
Consumer<Exception> failureHandler) {
131-
client.execute(CreateAndFollowIndexAction.INSTANCE, new CreateAndFollowIndexAction.Request(followRequest),
132-
ActionListener.wrap(r -> successHandler.run(), failureHandler));
135+
Client followerClient = CcrLicenseChecker.wrapClient(client, headers);
136+
CreateAndFollowIndexAction.Request request = new CreateAndFollowIndexAction.Request(followRequest);
137+
followerClient.execute(
138+
CreateAndFollowIndexAction.INSTANCE,
139+
request,
140+
ActionListener.wrap(r -> successHandler.run(), failureHandler)
141+
);
133142
}
134143

135144
@Override
136-
void updateAutoFollowMetadata(Function<ClusterState, ClusterState> updateFunction, Consumer<Exception> handler) {
145+
void updateAutoFollowMetadata(Function<ClusterState, ClusterState> updateFunction,
146+
Consumer<Exception> handler) {
137147
clusterService.submitStateUpdateTask("update_auto_follow_metadata", new ClusterStateUpdateTask() {
138148

139149
@Override
@@ -188,7 +198,7 @@ void autoFollowIndices() {
188198
AutoFollowPattern autoFollowPattern = entry.getValue();
189199
List<String> followedIndices = autoFollowMetadata.getFollowedLeaderIndexUUIDs().get(clusterAlias);
190200

191-
getLeaderClusterState(clusterAlias, (leaderClusterState, e) -> {
201+
getLeaderClusterState(autoFollowPattern.getHeaders(), clusterAlias, (leaderClusterState, e) -> {
192202
if (leaderClusterState != null) {
193203
assert e == null;
194204
handleClusterAlias(clusterAlias, autoFollowPattern, followedIndices, leaderClusterState);
@@ -251,7 +261,7 @@ private void handleClusterAlias(String clusterAlias, AutoFollowPattern autoFollo
251261
finalise(followError);
252262
}
253263
};
254-
createAndFollow(followRequest, successHandler, failureHandler);
264+
createAndFollow(autoFollowPattern.getHeaders(), followRequest, successHandler, failureHandler);
255265
}
256266
}
257267
}
@@ -314,14 +324,27 @@ static Function<ClusterState, ClusterState> recordLeaderIndexAsFollowFunction(St
314324
/**
315325
* Fetch the cluster state from the leader with the specified cluster alias
316326
*
327+
* @param headers the client headers
317328
* @param leaderClusterAlias the cluster alias of the leader
318329
* @param handler the callback to invoke
319330
*/
320-
abstract void getLeaderClusterState(String leaderClusterAlias, BiConsumer<ClusterState, Exception> handler);
321-
322-
abstract void createAndFollow(FollowIndexAction.Request followRequest, Runnable successHandler, Consumer<Exception> failureHandler);
323-
324-
abstract void updateAutoFollowMetadata(Function<ClusterState, ClusterState> updateFunction, Consumer<Exception> handler);
331+
abstract void getLeaderClusterState(
332+
Map<String, String> headers,
333+
String leaderClusterAlias,
334+
BiConsumer<ClusterState, Exception> handler
335+
);
336+
337+
abstract void createAndFollow(
338+
Map<String, String> headers,
339+
FollowIndexAction.Request followRequest,
340+
Runnable successHandler,
341+
Consumer<Exception> failureHandler
342+
);
343+
344+
abstract void updateAutoFollowMetadata(
345+
Function<ClusterState, ClusterState> updateFunction,
346+
Consumer<Exception> handler
347+
);
325348

326349
}
327350
}

0 commit comments

Comments
 (0)