Skip to content

Commit 58110d4

Browse files
also prevent direct access to backing failure indices with FLS/DLS
1 parent 80e904a commit 58110d4

File tree

3 files changed

+78
-6
lines changed

3 files changed

+78
-6
lines changed

x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.elasticsearch.common.Strings;
1818
import org.elasticsearch.common.settings.SecureString;
1919
import org.elasticsearch.common.settings.Settings;
20+
import org.elasticsearch.common.util.Maps;
2021
import org.elasticsearch.common.util.concurrent.ThreadContext;
2122
import org.elasticsearch.common.xcontent.XContentHelper;
2223
import org.elasticsearch.common.xcontent.support.XContentMapValues;
@@ -1448,7 +1449,21 @@ public void testDlsFls() throws Exception {
14481449

14491450
assertSearchResponseContainsExpectedIndicesAndFields(
14501451
performRequest(user, new Search("test1::failures").toSearchRequest()),
1451-
Map.of(failureIndexName, Set.of("@timestamp", "document", "error"))
1452+
Map.of(
1453+
failureIndexName,
1454+
Set.of(
1455+
"@timestamp",
1456+
"document.id",
1457+
"document.index",
1458+
"document.source.@timestamp",
1459+
"document.source.age",
1460+
"document.source.email",
1461+
"document.source.name",
1462+
"error.message",
1463+
"error.stack_trace",
1464+
"error.type"
1465+
)
1466+
)
14521467
);
14531468

14541469
// DLS
@@ -1502,6 +1517,40 @@ public void testDlsFls() throws Exception {
15021517
}""", role);
15031518
// DLS does not apply because there is a section without DLS
15041519
expectSearch(user, new Search(randomFrom("test1", "test1::data")), dataIndexDocId);
1520+
1521+
// check that direct access to backing failure store indices is not allowed
1522+
upsertRole(Strings.format("""
1523+
{
1524+
"cluster": ["all"],
1525+
"indices": [
1526+
{
1527+
"names": ["%s"],
1528+
"privileges": ["read"],
1529+
"field_security": {
1530+
"grant": ["@timestamp", "document.source.name"]
1531+
}
1532+
}
1533+
]
1534+
}""", failureIndexName), role);
1535+
// FLS is not applicable to backing failure store indices
1536+
expectFlsDlsError(() -> performRequest(user, new Search(failureIndexName).toSearchRequest()));
1537+
expectFlsDlsError(() -> performRequest(user, new Search(".fs-*").toSearchRequest()));
1538+
1539+
// DLS is not applicable to backing failure store, even when granted directly
1540+
upsertRole(Strings.format("""
1541+
{
1542+
"cluster": ["all"],
1543+
"indices": [
1544+
{
1545+
"names": ["%s"],
1546+
"privileges": ["read"],
1547+
"query":{"term":{"name":{"value":"jack"}}}
1548+
}
1549+
]
1550+
}""", failureIndexName), role);
1551+
expectFlsDlsError(() -> performRequest(user, new Search(failureIndexName).toSearchRequest()));
1552+
expectFlsDlsError(() -> performRequest(user, new Search(".fs-*").toSearchRequest()));
1553+
15051554
}
15061555

15071556
private static void expectThrows(ThrowingRunnable runnable, int statusCode) {
@@ -1792,7 +1841,7 @@ protected void assertSearchResponseContainsExpectedIndicesAndFields(
17921841
assertThat(searchResult.keySet(), equalTo(expectedIndicesAndFields.keySet()));
17931842
for (String index : expectedIndicesAndFields.keySet()) {
17941843
Set<String> expectedFields = expectedIndicesAndFields.get(index);
1795-
assertThat(searchResult.get(index).keySet(), equalTo(expectedFields));
1844+
assertThat(Maps.flatten(searchResult.get(index), false, true).keySet(), equalTo(expectedFields));
17961845
}
17971846
} finally {
17981847
response.decRef();

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ Collection<Object> createComponents(
11421142
)
11431143
);
11441144
if (DataStream.isFailureStoreFeatureFlagEnabled()) {
1145-
requestInterceptors.add(new FailureStoreRequestInterceptor(threadPool, getLicenseState()));
1145+
requestInterceptors.add(new FailureStoreRequestInterceptor(clusterService, projectResolver, threadPool, getLicenseState()));
11461146
}
11471147
}
11481148
requestInterceptors = Collections.unmodifiableSet(requestInterceptors);

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
import org.elasticsearch.action.ActionListener;
1212
import org.elasticsearch.action.IndicesRequest;
1313
import org.elasticsearch.action.support.IndexComponentSelector;
14+
import org.elasticsearch.cluster.metadata.DataStreamFailureStoreDefinition;
15+
import org.elasticsearch.cluster.metadata.IndexMetadata;
1416
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
17+
import org.elasticsearch.cluster.project.ProjectResolver;
18+
import org.elasticsearch.cluster.service.ClusterService;
1519
import org.elasticsearch.license.XPackLicenseState;
1620
import org.elasticsearch.rest.RestStatus;
1721
import org.elasticsearch.threadpool.ThreadPool;
@@ -21,8 +25,18 @@
2125

2226
public class FailureStoreRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor {
2327

24-
public FailureStoreRequestInterceptor(ThreadPool threadPool, XPackLicenseState licenseState) {
28+
private final ClusterService clusterService;
29+
private final ProjectResolver projectResolver;
30+
31+
public FailureStoreRequestInterceptor(
32+
ClusterService clusterService,
33+
ProjectResolver projectResolver,
34+
ThreadPool threadPool,
35+
XPackLicenseState licenseState
36+
) {
2537
super(threadPool.getThreadContext(), licenseState);
38+
this.clusterService = clusterService;
39+
this.projectResolver = projectResolver;
2640
}
2741

2842
@Override
@@ -32,7 +46,8 @@ void disableFeatures(
3246
ActionListener<Void> listener
3347
) {
3448
for (var indexAccessControl : indicesAccessControlByIndex.entrySet()) {
35-
if (hasFailuresSelectorSuffix(indexAccessControl.getKey()) && hasDlsFlsPermissions(indexAccessControl.getValue())) {
49+
if ((hasFailuresSelectorSuffix(indexAccessControl.getKey()) || isBackingFailureStoreIndex(indexAccessControl.getKey()))
50+
&& hasDlsFlsPermissions(indexAccessControl.getValue())) {
3651
listener.onFailure(
3752
new ElasticsearchSecurityException(
3853
"Failure store access is not allowed for users who have "
@@ -50,7 +65,7 @@ void disableFeatures(
5065
boolean supports(IndicesRequest request) {
5166
if (request.indicesOptions().allowSelectors()) {
5267
for (String index : request.indices()) {
53-
if (hasFailuresSelectorSuffix(index)) {
68+
if (hasFailuresSelectorSuffix(index) || isBackingFailureStoreIndex(index)) {
5469
return true;
5570
}
5671
}
@@ -70,4 +85,12 @@ private boolean hasDlsFlsPermissions(IndicesAccessControl.IndexAccessControl ind
7085
|| indexAccessControl.getFieldPermissions().hasFieldLevelSecurity();
7186
}
7287

88+
private boolean isBackingFailureStoreIndex(String index) {
89+
IndexMetadata indexMetadata = clusterService.state().metadata().getProject(projectResolver.getProjectId()).index(index);
90+
if (indexMetadata == null) {
91+
return false;
92+
}
93+
return indexMetadata.getSettings().hasValue(DataStreamFailureStoreDefinition.INDEX_FAILURE_STORE_VERSION_SETTING_NAME);
94+
}
95+
7396
}

0 commit comments

Comments
 (0)