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

This commit is contained in:
Jake Landis 2024-03-14 10:37:36 -05:00 committed by GitHub
parent 5a7b53a277
commit 5de1f176b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 164 additions and 1 deletions

View file

@ -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: []

View file

@ -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())
)
);
}

View file

@ -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();
}
}

View file

@ -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());
}
}