-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lazy compute the index access control (#88708)
The access control was always computed eagerly, even in cases when it was not necessary (e.g. shard is not accessed on the node doing the authorization or in cases when authorization is denied). This commit defers the computation to when it's really needed in order to avoid that the actual work is done on the network worker thread.
- Loading branch information
1 parent
30faac8
commit 06552d0
Showing
17 changed files
with
724 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...erTest/java/org/elasticsearch/integration/AbstractDocumentAndFieldLevelSecurityTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.integration; | ||
|
||
import org.elasticsearch.action.support.PlainActionFuture; | ||
import org.elasticsearch.client.internal.Client; | ||
import org.elasticsearch.license.GetFeatureUsageRequest; | ||
import org.elasticsearch.license.GetFeatureUsageResponse; | ||
import org.elasticsearch.license.TransportGetFeatureUsageAction; | ||
import org.elasticsearch.test.SecurityIntegTestCase; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
import static org.elasticsearch.xpack.core.security.SecurityField.DOCUMENT_LEVEL_SECURITY_FEATURE; | ||
import static org.elasticsearch.xpack.core.security.SecurityField.FIELD_LEVEL_SECURITY_FEATURE; | ||
import static org.hamcrest.Matchers.containsInAnyOrder; | ||
import static org.hamcrest.Matchers.everyItem; | ||
import static org.hamcrest.Matchers.in; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.not; | ||
|
||
abstract class AbstractDocumentAndFieldLevelSecurityTests extends SecurityIntegTestCase { | ||
|
||
private static final Set<String> DLS_FLS_FEATURE_NAMES = Set.of( | ||
DOCUMENT_LEVEL_SECURITY_FEATURE.getName(), | ||
FIELD_LEVEL_SECURITY_FEATURE.getName() | ||
); | ||
|
||
protected static void assertOnlyDlsTracked() { | ||
Set<String> features = fetchFeatureUsageFromAllNodes(); | ||
assertThat(DOCUMENT_LEVEL_SECURITY_FEATURE.getName(), is(in(features))); | ||
assertThat(FIELD_LEVEL_SECURITY_FEATURE.getName(), not(is(in(features)))); | ||
} | ||
|
||
protected static void assertOnlyFlsTracked() { | ||
Set<String> features = fetchFeatureUsageFromAllNodes(); | ||
assertThat(FIELD_LEVEL_SECURITY_FEATURE.getName(), is(in(features))); | ||
assertThat(DOCUMENT_LEVEL_SECURITY_FEATURE.getName(), not(is(in(features)))); | ||
} | ||
|
||
protected static void assertDlsFlsTracked() { | ||
assertThat(DLS_FLS_FEATURE_NAMES, everyItem(is(in(fetchFeatureUsageFromAllNodes())))); | ||
} | ||
|
||
protected static void assertDlsFlsNotTrackedAcrossAllNodes() { | ||
assertThat(fetchFeatureUsageFromAllNodes(), not(containsInAnyOrder(DLS_FLS_FEATURE_NAMES))); | ||
} | ||
|
||
protected static void assertDlsFlsNotTrackedOnCoordOnlyNode() { | ||
assertThat(fetchFeatureUsageFromCoordOnlyNode(), not(containsInAnyOrder(DLS_FLS_FEATURE_NAMES))); | ||
} | ||
|
||
protected static Set<String> fetchFeatureUsageFromAllNodes() { | ||
final Set<String> result = new HashSet<>(); | ||
// Nodes are chosen at random when test is executed, | ||
// hence we have to aggregate feature usage across all nodes in the cluster. | ||
Set.of(internalCluster().getNodeNames()).stream().forEach(node -> { result.addAll(fetchFeatureUsageFromNode(client(node))); }); | ||
return result; | ||
} | ||
|
||
protected static Set<String> fetchFeatureUsageFromCoordOnlyNode() { | ||
return fetchFeatureUsageFromNode(internalCluster().coordOnlyNodeClient()); | ||
} | ||
|
||
protected static Set<String> fetchFeatureUsageFromNode(Client client) { | ||
final Set<String> result = new HashSet<>(); | ||
PlainActionFuture<GetFeatureUsageResponse> listener = new PlainActionFuture<>(); | ||
client.execute(TransportGetFeatureUsageAction.TYPE, new GetFeatureUsageRequest(), listener); | ||
GetFeatureUsageResponse response = listener.actionGet(); | ||
for (var feature : response.getFeatures()) { | ||
result.add(feature.getName()); | ||
} | ||
return result; | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
...lusterTest/java/org/elasticsearch/integration/DocumentLevelSecurityFeatureUsageTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
package org.elasticsearch.integration; | ||
|
||
import org.elasticsearch.action.search.SearchResponse; | ||
import org.elasticsearch.common.settings.SecureString; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.core.Strings; | ||
import org.elasticsearch.index.query.QueryBuilders; | ||
import org.elasticsearch.test.ESIntegTestCase; | ||
import org.elasticsearch.test.SecuritySettingsSourceField; | ||
import org.elasticsearch.xpack.core.XPackSettings; | ||
|
||
import java.util.Collections; | ||
|
||
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; | ||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; | ||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; | ||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; | ||
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER; | ||
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; | ||
|
||
@ESIntegTestCase.ClusterScope(numClientNodes = 1) | ||
public class DocumentLevelSecurityFeatureUsageTests extends AbstractDocumentAndFieldLevelSecurityTests { | ||
|
||
protected static final SecureString USERS_PASSWD = SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING; | ||
|
||
@Override | ||
protected String configUsers() { | ||
final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD)); | ||
return super.configUsers() + Strings.format(""" | ||
user1:%s | ||
user2:%s | ||
""", usersPasswdHashed, usersPasswdHashed); | ||
} | ||
|
||
@Override | ||
protected String configUsersRoles() { | ||
return super.configUsersRoles() + """ | ||
role1:user1 | ||
role2:user1 | ||
role3:user2 | ||
"""; | ||
} | ||
|
||
@Override | ||
protected String configRoles() { | ||
return super.configRoles() + """ | ||
role1: | ||
cluster: [ none ] | ||
indices: | ||
- names: '*' | ||
privileges: [ none ] | ||
role2: | ||
cluster: | ||
- all | ||
indices: | ||
- names: '*' | ||
privileges: | ||
- all | ||
query: | ||
term: | ||
field1: value1 | ||
role3: | ||
cluster: [ all ] | ||
indices: | ||
- names: '*' | ||
privileges: [ ALL ] | ||
"""; | ||
} | ||
|
||
@Override | ||
public Settings nodeSettings(int nodeOrdinal, Settings otherSettings) { | ||
return Settings.builder() | ||
.put(super.nodeSettings(nodeOrdinal, otherSettings)) | ||
.put(XPackSettings.DLS_FLS_ENABLED.getKey(), true) | ||
.put(XPackSettings.AUDIT_ENABLED.getKey(), false) // Just to make logs less noisy | ||
.build(); | ||
} | ||
|
||
public void testDlsFeatureUsageTracking() throws Exception { | ||
assertAcked( | ||
client().admin().indices().prepareCreate("test").setMapping("field1", "type=text", "field2", "type=text", "field3", "type=text") | ||
); | ||
client().prepareIndex("test").setId("1").setSource("field1", "value1").setRefreshPolicy(IMMEDIATE).get(); | ||
client().prepareIndex("test").setId("2").setSource("field2", "value2").setRefreshPolicy(IMMEDIATE).get(); | ||
client().prepareIndex("test").setId("3").setSource("field3", "value3").setRefreshPolicy(IMMEDIATE).get(); | ||
|
||
SearchResponse response = internalCluster().coordOnlyNodeClient() | ||
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) | ||
.prepareSearch("test") | ||
.setQuery(randomBoolean() ? QueryBuilders.termQuery("field1", "value1") : QueryBuilders.matchAllQuery()) | ||
.get(); | ||
assertHitCount(response, 1); | ||
assertSearchHits(response, "1"); | ||
|
||
// coordinating only node should not tack DLS/FLS feature usage | ||
assertDlsFlsNotTrackedOnCoordOnlyNode(); | ||
// only DLS feature should have been tracked across all data nodes | ||
assertOnlyDlsTracked(); | ||
} | ||
|
||
public void testDlsFlsFeatureUsageNotTracked() { | ||
assertAcked( | ||
client().admin().indices().prepareCreate("test").setMapping("id", "type=keyword", "field1", "type=text", "field2", "type=text") | ||
); | ||
client().prepareIndex("test").setId("1").setSource("id", "1", "field1", "value1").setRefreshPolicy(IMMEDIATE).get(); | ||
client().prepareIndex("test").setId("2").setSource("id", "2", "field2", "value2").setRefreshPolicy(IMMEDIATE).get(); | ||
|
||
// Running a search with user2 (which has role3 without DLS/FLS) should not trigger feature tracking. | ||
SearchResponse response = internalCluster().coordOnlyNodeClient() | ||
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) | ||
.prepareSearch("test") | ||
.get(); | ||
assertHitCount(response, 2); | ||
assertSearchHits(response, "1", "2"); | ||
|
||
assertDlsFlsNotTrackedAcrossAllNodes(); | ||
} | ||
|
||
} |
Oops, something went wrong.