Browse Source

disable validate when rewrite parameter is sent and the index access control list is non-null (#105709)

Jake Landis 1 year ago
parent
commit
5de1f176b4

+ 6 - 0
docs/changelog/105709.yaml

@@ -0,0 +1,6 @@
+pr: 105709
+summary: Disable validate when rewrite parameter is sent and the index access control
+  list is non-null
+area: Security
+type: bug
+issues: []

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

@@ -303,6 +303,7 @@ import org.elasticsearch.xpack.security.authz.interceptor.SearchRequestCacheDisa
 import org.elasticsearch.xpack.security.authz.interceptor.SearchRequestInterceptor;
 import org.elasticsearch.xpack.security.authz.interceptor.ShardSearchRequestInterceptor;
 import org.elasticsearch.xpack.security.authz.interceptor.UpdateRequestInterceptor;
+import org.elasticsearch.xpack.security.authz.interceptor.ValidateRequestInterceptor;
 import org.elasticsearch.xpack.security.authz.restriction.WorkflowService;
 import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
 import org.elasticsearch.xpack.security.authz.store.DeprecationRoleDescriptorConsumer;
@@ -999,7 +1000,8 @@ public class Security extends Plugin
                     new UpdateRequestInterceptor(threadPool, getLicenseState()),
                     new BulkShardRequestInterceptor(threadPool, getLicenseState()),
                     new DlsFlsLicenseRequestInterceptor(threadPool.getThreadContext(), getLicenseState()),
-                    new SearchRequestCacheDisablingInterceptor(threadPool, getLicenseState())
+                    new SearchRequestCacheDisablingInterceptor(threadPool, getLicenseState()),
+                    new ValidateRequestInterceptor(threadPool, getLicenseState())
                 )
             );
         }

+ 61 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/ValidateRequestInterceptor.java

@@ -0,0 +1,61 @@
+/*
+ * 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.xpack.security.authz.interceptor;
+
+import org.elasticsearch.ElasticsearchSecurityException;
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.IndicesRequest;
+import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
+import org.elasticsearch.license.XPackLicenseState;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
+
+import java.util.Map;
+
+public class ValidateRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor {
+
+    public ValidateRequestInterceptor(ThreadPool threadPool, XPackLicenseState licenseState) {
+        super(threadPool.getThreadContext(), licenseState);
+    }
+
+    @Override
+    void disableFeatures(
+        IndicesRequest indicesRequest,
+        Map<String, IndicesAccessControl.IndexAccessControl> indexAccessControlByIndex,
+        ActionListener<Void> listener
+    ) {
+        final ValidateQueryRequest request = (ValidateQueryRequest) indicesRequest;
+        if (indexAccessControlByIndex.values().stream().anyMatch(iac -> iac.getDocumentPermissions().hasDocumentLevelPermissions())) {
+            if (hasRewrite(request)) {
+                listener.onFailure(
+                    new ElasticsearchSecurityException(
+                        "Validate with rewrite isn't supported if document level security is enabled",
+                        RestStatus.BAD_REQUEST
+                    )
+                );
+            } else {
+                listener.onResponse(null);
+            }
+        } else {
+            listener.onResponse(null);
+        }
+    }
+
+    @Override
+    public boolean supports(IndicesRequest request) {
+        if (request instanceof ValidateQueryRequest validateQueryRequest) {
+            return hasRewrite(validateQueryRequest);
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean hasRewrite(ValidateQueryRequest validateQueryRequest) {
+        return validateQueryRequest.rewrite();
+    }
+}

+ 94 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/interceptor/ValidateRequestInterceptorTests.java

@@ -0,0 +1,94 @@
+/*
+ * 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.xpack.security.authz.interceptor;
+
+import org.elasticsearch.ElasticsearchSecurityException;
+import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequestBuilder;
+import org.elasticsearch.action.support.PlainActionFuture;
+import org.elasticsearch.client.internal.ElasticsearchClient;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.license.MockLicenseState;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.threadpool.TestThreadPool;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
+import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions;
+import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.xpack.core.security.SecurityField.DOCUMENT_LEVEL_SECURITY_FEATURE;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ValidateRequestInterceptorTests extends ESTestCase {
+
+    private ThreadPool threadPool;
+    private MockLicenseState licenseState;
+    private ValidateRequestInterceptor interceptor;
+
+    @Before
+    public void init() {
+        threadPool = new TestThreadPool("validate request interceptor tests");
+        licenseState = mock(MockLicenseState.class);
+        when(licenseState.isAllowed(DOCUMENT_LEVEL_SECURITY_FEATURE)).thenReturn(true);
+        interceptor = new ValidateRequestInterceptor(threadPool, licenseState);
+    }
+
+    @After
+    public void stopThreadPool() {
+        terminate(threadPool);
+    }
+
+    public void testValidateRequestWithDLS() {
+        final DocumentPermissions documentPermissions = DocumentPermissions.filteredBy(Set.of(new BytesArray("""
+            {"term":{"username":"foo"}}"""))); // value does not matter
+        ElasticsearchClient client = mock(ElasticsearchClient.class);
+        ValidateQueryRequestBuilder builder = new ValidateQueryRequestBuilder(client);
+        final String index = randomAlphaOfLengthBetween(3, 8);
+        final PlainActionFuture<Void> listener1 = new PlainActionFuture<>();
+        Map<String, IndicesAccessControl.IndexAccessControl> accessControlMap = Map.of(
+            index,
+            new IndicesAccessControl.IndexAccessControl(FieldPermissions.DEFAULT, documentPermissions)
+        );
+        // with DLS and rewrite enabled
+        interceptor.disableFeatures(builder.setRewrite(true).request(), accessControlMap, listener1);
+        ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class, () -> listener1.actionGet());
+        assertThat(exception.getMessage(), containsString("Validate with rewrite isn't supported if document level security is enabled"));
+
+        // with DLS and rewrite disabled
+        final PlainActionFuture<Void> listener2 = new PlainActionFuture<>();
+        interceptor.disableFeatures(builder.setRewrite(false).request(), accessControlMap, listener2);
+        assertNull(listener2.actionGet());
+
+    }
+
+    public void testValidateRequestWithOutDLS() {
+        final DocumentPermissions documentPermissions = null; // no DLS
+        ElasticsearchClient client = mock(ElasticsearchClient.class);
+        ValidateQueryRequestBuilder builder = new ValidateQueryRequestBuilder(client);
+        final String index = randomAlphaOfLengthBetween(3, 8);
+        final PlainActionFuture<Void> listener1 = new PlainActionFuture<>();
+        Map<String, IndicesAccessControl.IndexAccessControl> accessControlMap = Map.of(
+            index,
+            new IndicesAccessControl.IndexAccessControl(FieldPermissions.DEFAULT, documentPermissions)
+        );
+        // without DLS and rewrite enabled
+        interceptor.disableFeatures(builder.setRewrite(true).request(), accessControlMap, listener1);
+        assertNull(listener1.actionGet());
+
+        // without DLS and rewrite disabled
+        final PlainActionFuture<Void> listener2 = new PlainActionFuture<>();
+        interceptor.disableFeatures(builder.setRewrite(false).request(), accessControlMap, listener2);
+        assertNull(listener2.actionGet());
+    }
+}