Skip to content

Commit 111afab

Browse files
authored
Improve shard level request cache efficiency (#69505) (#69522)
Shard level request cache is improved to work correctly at all time. Also ensure profiling and suggester are properly disabled when not supported.
1 parent 0d8d346 commit 111afab

File tree

10 files changed

+188
-25
lines changed

10 files changed

+188
-25
lines changed

server/src/main/java/org/elasticsearch/search/internal/ShardSearchLocalRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ public Boolean requestCache() {
170170
return requestCache;
171171
}
172172

173+
@Override
174+
public void requestCache(Boolean requestCache) {
175+
this.requestCache = requestCache;
176+
}
177+
173178
@Override
174179
public Boolean allowPartialSearchResults() {
175180
return allowPartialSearchResults;

server/src/main/java/org/elasticsearch/search/internal/ShardSearchRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public interface ShardSearchRequest {
6969

7070
Boolean requestCache();
7171

72+
void requestCache(Boolean requestCache);
73+
7274
Boolean allowPartialSearchResults();
7375

7476
Scroll scroll();

server/src/main/java/org/elasticsearch/search/internal/ShardSearchTransportRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ public Boolean requestCache() {
137137
return shardSearchLocalRequest.requestCache();
138138
}
139139

140+
@Override
141+
public void requestCache(Boolean requestCache) {
142+
shardSearchLocalRequest.requestCache(requestCache);
143+
}
144+
140145
@Override
141146
public Boolean allowPartialSearchResults() {
142147
return shardSearchLocalRequest.allowPartialSearchResults();

server/src/test/java/org/elasticsearch/index/SearchSlowLogTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ public Boolean requestCache() {
145145
return null;
146146
}
147147

148+
@Override
149+
public void requestCache(Boolean requestCache) {
150+
}
151+
148152
@Override
149153
public Boolean allowPartialSearchResults() {
150154
return null;

server/src/test/java/org/elasticsearch/search/slice/SliceBuilderTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ public Boolean requestCache() {
162162
return null;
163163
}
164164

165+
@Override
166+
public void requestCache(Boolean requestCache) {
167+
}
168+
165169
@Override
166170
public Boolean allowPartialSearchResults() {
167171
return null;

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import org.apache.logging.log4j.Logger;
1010
import org.elasticsearch.action.ActionListener;
1111
import org.elasticsearch.action.IndicesRequest;
12+
import org.elasticsearch.common.Strings;
1213
import org.elasticsearch.common.util.concurrent.ThreadContext;
1314
import org.elasticsearch.license.XPackLicenseState;
15+
import org.elasticsearch.transport.TransportActionProxy;
1416
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
1517
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo;
1618
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.RequestInfo;
@@ -36,26 +38,35 @@ abstract class FieldAndDocumentLevelSecurityRequestInterceptor implements Reques
3638
@Override
3739
public void intercept(RequestInfo requestInfo, AuthorizationEngine authorizationEngine, AuthorizationInfo authorizationInfo,
3840
ActionListener<Void> listener) {
39-
if (requestInfo.getRequest() instanceof IndicesRequest) {
41+
if (requestInfo.getRequest() instanceof IndicesRequest && false == TransportActionProxy.isProxyAction(requestInfo.getAction())) {
4042
IndicesRequest indicesRequest = (IndicesRequest) requestInfo.getRequest();
4143
if (supports(indicesRequest) && licenseState.isDocumentAndFieldLevelSecurityAllowed()) {
4244
final IndicesAccessControl indicesAccessControl =
4345
threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY);
44-
for (String index : indicesRequest.indices()) {
46+
boolean fieldLevelSecurityEnabled = false;
47+
boolean documentLevelSecurityEnabled = false;
48+
final String[] requestIndices = indicesRequest.indices();
49+
for (String index : requestIndices) {
4550
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index);
4651
if (indexAccessControl != null) {
47-
boolean fieldLevelSecurityEnabled = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity();
48-
boolean documentLevelSecurityEnabled = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions();
49-
if (fieldLevelSecurityEnabled || documentLevelSecurityEnabled) {
50-
logger.trace("intercepted request for index [{}] with field level access controls [{}] " +
51-
"document level access controls [{}]. disabling conflicting features",
52-
index, fieldLevelSecurityEnabled, documentLevelSecurityEnabled);
53-
disableFeatures(indicesRequest, fieldLevelSecurityEnabled, documentLevelSecurityEnabled, listener);
54-
return;
52+
fieldLevelSecurityEnabled =
53+
fieldLevelSecurityEnabled || indexAccessControl.getFieldPermissions().hasFieldLevelSecurity();
54+
documentLevelSecurityEnabled =
55+
documentLevelSecurityEnabled || indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions();
56+
if (fieldLevelSecurityEnabled && documentLevelSecurityEnabled) {
57+
break;
5558
}
5659
}
57-
logger.trace("intercepted request for index [{}] without field or document level access controls", index);
5860
}
61+
if (fieldLevelSecurityEnabled || documentLevelSecurityEnabled) {
62+
logger.trace("intercepted request for indices [{}] with field level access controls [{}] " +
63+
"document level access controls [{}]. disabling conflicting features",
64+
Strings.arrayToDelimitedString(requestIndices, ","), fieldLevelSecurityEnabled, documentLevelSecurityEnabled);
65+
disableFeatures(indicesRequest, fieldLevelSecurityEnabled, documentLevelSecurityEnabled, listener);
66+
return;
67+
}
68+
logger.trace("intercepted request for indices [{}] without field or document level access controls",
69+
Strings.arrayToDelimitedString(requestIndices, ","));
5970
}
6071
}
6172
listener.onResponse(null);

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/SearchRequestInterceptor.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.elasticsearch.action.search.SearchRequest;
1212
import org.elasticsearch.license.XPackLicenseState;
1313
import org.elasticsearch.rest.RestStatus;
14+
import org.elasticsearch.search.builder.SearchSourceBuilder;
15+
import org.elasticsearch.search.internal.ShardSearchTransportRequest;
1416
import org.elasticsearch.threadpool.ThreadPool;
1517

1618
/**
@@ -25,14 +27,26 @@ public SearchRequestInterceptor(ThreadPool threadPool, XPackLicenseState license
2527
@Override
2628
public void disableFeatures(IndicesRequest indicesRequest, boolean fieldLevelSecurityEnabled, boolean documentLevelSecurityEnabled,
2729
ActionListener<Void> listener) {
28-
final SearchRequest request = (SearchRequest) indicesRequest;
29-
request.requestCache(false);
30+
31+
assert indicesRequest instanceof SearchRequest || indicesRequest instanceof ShardSearchTransportRequest
32+
: "request must be either SearchRequest or ShardSearchTransportRequest";
33+
34+
final SearchSourceBuilder source;
35+
if (indicesRequest instanceof SearchRequest) {
36+
final SearchRequest request = (SearchRequest) indicesRequest;
37+
request.requestCache(false);
38+
source = request.source();
39+
} else {
40+
final ShardSearchTransportRequest request = (ShardSearchTransportRequest) indicesRequest;
41+
request.requestCache(false);
42+
source = request.source();
43+
}
3044

3145
if (documentLevelSecurityEnabled) {
32-
if (request.source() != null && request.source().suggest() != null) {
46+
if (source != null && source.suggest() != null) {
3347
listener.onFailure(new ElasticsearchSecurityException("Suggest isn't supported if document level security is enabled",
3448
RestStatus.BAD_REQUEST));
35-
} else if (request.source() != null && request.source().profile()) {
49+
} else if (source != null && source.profile()) {
3650
listener.onFailure(new ElasticsearchSecurityException("A search request cannot be profiled if document level security " +
3751
"is enabled", RestStatus.BAD_REQUEST));
3852
} else {
@@ -45,6 +59,6 @@ public void disableFeatures(IndicesRequest indicesRequest, boolean fieldLevelSec
4559

4660
@Override
4761
public boolean supports(IndicesRequest request) {
48-
return request instanceof SearchRequest;
62+
return request instanceof SearchRequest || request instanceof ShardSearchTransportRequest;
4963
}
5064
}

x-pack/plugin/security/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ protected String configUsers() {
9898
"user1:" + usersPasswdHashed + "\n" +
9999
"user2:" + usersPasswdHashed + "\n" +
100100
"user3:" + usersPasswdHashed + "\n" +
101-
"user4:" + usersPasswdHashed + "\n";
101+
"user4:" + usersPasswdHashed + "\n" +
102+
"user5:" + usersPasswdHashed + "\n";
102103
}
103104

104105
@Override
@@ -107,7 +108,8 @@ protected String configUsersRoles() {
107108
"role1:user1,user2,user3\n" +
108109
"role2:user1,user3\n" +
109110
"role3:user2,user3\n" +
110-
"role4:user4\n";
111+
"role4:user4\n" +
112+
"role5:user5\n";
111113
}
112114

113115
@Override
@@ -140,7 +142,18 @@ protected String configRoles() {
140142
" - names: '*'\n" +
141143
" privileges: [ ALL ]\n" +
142144
// query that can match nested documents
143-
" query: '{\"bool\": { \"must_not\": { \"term\" : {\"field1\" : \"value2\"}}}}'";
145+
" query: '{\"bool\": { \"must_not\": { \"term\" : {\"field1\" : \"value2\"}}}}'\n" +
146+
"role5:\n" +
147+
" cluster: [ all ]\n" +
148+
" indices:\n" +
149+
" - names: [ 'test' ]\n" +
150+
" privileges: [ read ]\n" +
151+
" query: '{\"term\" : {\"field2\" : \"value2\"}}'\n" +
152+
" - names: [ 'fls-index' ]\n" +
153+
" privileges: [ read ]\n" +
154+
" field_security:\n" +
155+
" grant: [ 'field1', 'other_field', 'suggest_field2' ]\n";
156+
144157
}
145158

146159
@Override
@@ -922,6 +935,15 @@ public void testSuggesters() throws Exception {
922935
.endObject()).get();
923936
refresh("test");
924937

938+
assertAcked(client().admin().indices().prepareCreate("fls-index")
939+
.setSettings(Settings.builder()
940+
.put("index.number_of_shards", 1)
941+
.put("index.number_of_replicas", 0)
942+
)
943+
.addMapping("type1", "field1", "type=text", "suggest_field1", "type=text", "suggest_field2", "type=completion",
944+
"yet_another", "type=text")
945+
);
946+
925947
// Term suggester:
926948
SearchResponse response = client()
927949
.prepareSearch("test")
@@ -937,9 +959,12 @@ public void testSuggesters() throws Exception {
937959
assertThat(termSuggestion.getEntries().get(0).getOptions().size(), equalTo(1));
938960
assertThat(termSuggestion.getEntries().get(0).getOptions().get(0).getText().string(), equalTo("value"));
939961

962+
final String[] indices =
963+
randomFrom(Arrays.asList(new String[] { "test" }, new String[] { "fls-index", "test" }, new String[] { "test", "fls-index" }));
964+
940965
Exception e = expectThrows(ElasticsearchSecurityException.class, () -> client()
941-
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
942-
.prepareSearch("test")
966+
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD)))
967+
.prepareSearch(indices)
943968
.suggest(new SuggestBuilder()
944969
.setGlobalText("valeu")
945970
.addSuggestion("_name1", new TermSuggestionBuilder("suggest_field1"))
@@ -962,7 +987,7 @@ public void testSuggesters() throws Exception {
962987
assertThat(phraseSuggestion.getEntries().get(0).getOptions().get(0).getText().string(), equalTo("value"));
963988

964989
e = expectThrows(ElasticsearchSecurityException.class, () -> client()
965-
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
990+
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD)))
966991
.prepareSearch("test")
967992
.suggest(new SuggestBuilder()
968993
.setGlobalText("valeu")
@@ -986,7 +1011,7 @@ public void testSuggesters() throws Exception {
9861011
assertThat(completionSuggestion.getEntries().get(0).getOptions().get(0).getText().string(), equalTo("value"));
9871012

9881013
e = expectThrows(ElasticsearchSecurityException.class, () -> client()
989-
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
1014+
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD)))
9901015
.prepareSearch("test")
9911016
.suggest(new SuggestBuilder()
9921017
.setGlobalText("valeu")
@@ -1017,6 +1042,15 @@ public void testProfile() throws Exception {
10171042
.endObject()).get();
10181043
refresh("test");
10191044

1045+
assertAcked(client().admin().indices().prepareCreate("fls-index")
1046+
.setSettings(Settings.builder()
1047+
.put("index.number_of_shards", 1)
1048+
.put("index.number_of_replicas", 0)
1049+
)
1050+
.addMapping("type1", "field1", "type=text", "suggest_field1", "type=text", "suggest_field2", "type=completion",
1051+
"yet_another", "type=text")
1052+
);
1053+
10201054
SearchResponse response = client()
10211055
.prepareSearch("test")
10221056
.setProfile(true)
@@ -1033,9 +1067,11 @@ public void testProfile() throws Exception {
10331067
// ProfileResult profileResult = queryProfileShardResult.getQueryResults().get(0);
10341068
// assertThat(profileResult.getLuceneDescription(), equalTo("(other_field:value)^0.8"));
10351069

1070+
final String[] indices =
1071+
randomFrom(Arrays.asList(new String[] { "test" }, new String[] { "fls-index", "test" }, new String[] { "test", "fls-index" }));
10361072
Exception e = expectThrows(ElasticsearchSecurityException.class, () -> client()
1037-
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
1038-
.prepareSearch("test")
1073+
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD)))
1074+
.prepareSearch(indices)
10391075
.setProfile(true)
10401076
.setQuery(new FuzzyQueryBuilder("other_field", "valeu"))
10411077
.get());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
setup:
3+
- skip:
4+
features: headers
5+
6+
- do:
7+
cluster.health:
8+
wait_for_status: yellow
9+
10+
- do:
11+
xpack.security.put_user:
12+
username: "dls_fls_user"
13+
body: >
14+
{
15+
"password": "s3krit-password",
16+
"roles" : [ "dls_fls_role" ]
17+
}
18+
19+
---
20+
teardown:
21+
- do:
22+
xpack.security.delete_user:
23+
username: "dls_fls_user"
24+
ignore: 404
25+
26+
---
27+
"Search with document and field level security":
28+
- do:
29+
search:
30+
rest_total_hits_as_int: true
31+
request_cache: true
32+
index: my_remote_cluster:shared_index
33+
34+
- match: { hits.total: 2}
35+
- length: { hits.hits.0._source: 3 }
36+
- match: { hits.hits.0._source.secret: "sesame" }
37+
38+
- do:
39+
headers: { Authorization: "Basic ZGxzX2Zsc191c2VyOnMza3JpdC1wYXNzd29yZA==" }
40+
search:
41+
rest_total_hits_as_int: true
42+
request_cache: true
43+
index: my_remote_cluster:shared_index
44+
45+
- match: { hits.total: 1}
46+
- length: { hits.hits.0._source: 2 }
47+
- is_true: hits.hits.0._source.public
48+
- match: { hits.hits.0._source.name: "doc 1" }

x-pack/qa/multi-cluster-search-security/src/test/resources/rest-api-spec/test/remote_cluster/10_basic.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ setup:
2828
}
2929
]
3030
}
31+
32+
- do:
33+
xpack.security.put_role:
34+
name: "dls_fls_role"
35+
body: >
36+
{
37+
"cluster": ["monitor"],
38+
"indices": [
39+
{
40+
"names": ["shared_index"],
41+
"privileges": ["read", "read_cross_cluster"],
42+
"query": "{ \"term\": { \"public\" : true } }",
43+
"field_security": { "grant": ["*"], "except": ["secret"] }
44+
}
45+
]
46+
}
3147
---
3248
"Index data and search on the remote cluster":
3349

@@ -196,3 +212,21 @@ setup:
196212
"roles" : [ ]
197213
}
198214
- match: { user: { created: false } }
215+
216+
- do:
217+
indices.create:
218+
index: shared_index
219+
body:
220+
settings:
221+
index:
222+
number_of_shards: 1
223+
number_of_replicas: 0
224+
225+
- do:
226+
bulk:
227+
refresh: true
228+
body:
229+
- '{"index": {"_index": "shared_index", "_id": 1, "_type": "test_type"}}'
230+
- '{"public": true, "name": "doc 1", "secret": "sesame"}'
231+
- '{"index": {"_index": "shared_index", "_id": 2, "_type": "test_type"}}'
232+
- '{"public": false, "name": "doc 2", "secret": "sesame"}'

0 commit comments

Comments
 (0)