From 6bac470df67b16a22f496b052776e7afdd50e900 Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Thu, 27 Jul 2023 17:26:38 +0200 Subject: [PATCH 01/12] Clean up REST API (Part 1) (#2900) ### Description The aim of this PR is to start cleaning code in REST API since with the current implementation is difficult to understand and support. Changes: - Implemented new `RequestConetnValidator` class which uses the same validation logic as `AbstractConfigurationValidator` - Removed all redundant `AbstractConfigurationValidator` extensions ### Testing [Please provide details of testing done: unit testing, integration testing and manual testing] ### Check List - [ ] New functionality includes testing - [ ] New functionality has been documented - [ ] Commits are signed per the DCO using --signoff By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). --------- Signed-off-by: Andrey Pleskach --- .../dlic/rest/api/AbstractApiAction.java | 105 ++--- .../dlic/rest/api/AccountApiAction.java | 39 +- .../dlic/rest/api/ActionGroupsApiAction.java | 50 ++- .../dlic/rest/api/AllowlistApiAction.java | 35 +- .../dlic/rest/api/AuditApiAction.java | 96 ++++- .../rest/api/AuthTokenProcessorAction.java | 8 +- .../dlic/rest/api/FlushCacheApiAction.java | 21 +- .../dlic/rest/api/InternalUsersApiAction.java | 60 ++- .../dlic/rest/api/MigrateApiAction.java | 27 +- .../rest/api/MultiTenancyConfigApiAction.java | 68 +++- .../dlic/rest/api/NodesDnApiAction.java | 33 +- .../rest/api/PatchableResourceApiAction.java | 46 +-- .../dlic/rest/api/RolesApiAction.java | 91 ++++- .../dlic/rest/api/RolesMappingApiAction.java | 40 +- .../dlic/rest/api/SecurityConfigAction.java | 26 +- .../dlic/rest/api/SecuritySSLCertsAction.java | 5 +- .../dlic/rest/api/TenantsApiAction.java | 30 +- .../dlic/rest/api/ValidateApiAction.java | 18 +- .../AbstractConfigurationValidator.java | 353 ---------------- .../rest/validation/AccountValidator.java | 27 -- .../rest/validation/ActionGroupValidator.java | 37 -- .../rest/validation/AllowlistValidator.java | 26 -- .../dlic/rest/validation/AuditValidator.java | 83 ---- .../rest/validation/CredentialsValidator.java | 83 ---- .../validation/InternalUsersValidator.java | 37 -- .../MultiTenancyConfigValidator.java | 31 -- .../dlic/rest/validation/NoOpValidator.java | 24 -- .../rest/validation/NodesDnValidator.java | 27 -- .../rest/validation/PasswordValidator.java | 30 +- .../validation/RequestContentValidator.java | 382 ++++++++++++++++++ .../validation/RolesMappingValidator.java | 41 -- .../dlic/rest/validation/RolesValidator.java | 85 ---- .../validation/SecurityConfigValidator.java | 26 -- .../dlic/rest/validation/TenantValidator.java | 49 --- .../rest/validation/ValidationResult.java | 70 ++++ .../rest/validation/WhitelistValidator.java | 26 -- .../dlic/rest/api/ActionGroupsApiTest.java | 8 +- .../dlic/rest/api/AllowlistApiTest.java | 4 +- .../dlic/rest/api/NodesDnApiTest.java | 4 +- .../security/dlic/rest/api/RolesApiTest.java | 37 +- .../dlic/rest/api/RolesMappingApiTest.java | 26 +- .../security/dlic/rest/api/UserApiTest.java | 28 +- .../dlic/rest/api/WhitelistApiTest.java | 4 +- .../validation/PasswordValidatorTest.java | 46 +-- .../RequestContentValidatorTest.java | 320 +++++++++++++++ 45 files changed, 1405 insertions(+), 1307 deletions(-) delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/AccountValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/AllowlistValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/NodesDnValidator.java create mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java create mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java delete mode 100644 src/main/java/org/opensearch/security/dlic/rest/validation/WhitelistValidator.java create mode 100644 src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java index 727624e4e4..aeeb4f1c92 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collections; import java.util.Objects; @@ -30,7 +29,6 @@ import org.opensearch.client.Client; import org.opensearch.client.node.NodeClient; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext.StoredContext; import org.opensearch.common.xcontent.XContentHelper; @@ -47,14 +45,12 @@ import org.opensearch.core.rest.RestStatus; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.action.configupdate.ConfigUpdateAction; -import org.opensearch.security.action.configupdate.ConfigUpdateNodeResponse; import org.opensearch.security.action.configupdate.ConfigUpdateRequest; import org.opensearch.security.action.configupdate.ConfigUpdateResponse; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator.ErrorType; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.impl.CType; @@ -68,7 +64,7 @@ public abstract class AbstractApiAction extends BaseRestHandler { - protected final Logger log = LogManager.getLogger(this.getClass()); + private final static Logger LOGGER = LogManager.getLogger(AbstractApiAction.class); protected final ConfigurationRepository cl; protected final ClusterService cs; @@ -119,7 +115,7 @@ protected AbstractApiAction( this.auditLog = auditLog; } - protected abstract AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params); + protected abstract RequestContentValidator createValidator(final Object... params); protected abstract String getResourceName(); @@ -128,25 +124,22 @@ protected AbstractApiAction( protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException { try { - // validate additional settings, if any - AbstractConfigurationValidator validator = getValidator(request, request.content()); - if (!validator.validate()) { - request.params().clear(); - badRequestResponse(channel, validator); - return; - } switch (request.method()) { case DELETE: - handleDelete(channel, request, client, validator.getContentAsNode()); + handleDelete(channel, request, client, null); break; case POST: - handlePost(channel, request, client, validator.getContentAsNode()); + createValidator().validate(request) + .valid(jsonContent -> handlePost(channel, request, client, jsonContent)) + .error(toXContent -> requestContentInvalid(request, channel, toXContent)); break; case PUT: - handlePut(channel, request, client, validator.getContentAsNode()); + createValidator().validate(request) + .valid(jsonContent -> handlePut(channel, request, client, jsonContent)) + .error(toXContent -> requestContentInvalid(request, channel, toXContent)); break; case GET: - handleGet(channel, request, client, validator.getContentAsNode()); + handleGet(channel, request, client, null); break; default: throw new IllegalArgumentException(request.method() + " not supported"); @@ -160,6 +153,11 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } } + protected void requestContentInvalid(final RestRequest request, final RestChannel channel, final ToXContent toXContent) { + request.params().clear(); + badRequestResponse(channel, toXContent); + } + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { final String name = request.param("name"); @@ -200,16 +198,12 @@ public void onResponse(IndexResponse response) { protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - final String name = request.param("name"); - if (name == null || name.length() == 0) { badRequestResponse(channel, "No " + getResourceName() + " specified."); return; } - final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); - if (existingConfiguration.getSeqNo() < 0) { forbidden( channel, @@ -227,8 +221,8 @@ protected void handlePut(final RestChannel channel, final RestRequest request, f return; } - if (log.isTraceEnabled() && content != null) { - log.trace(content.toString()); + if (LOGGER.isTraceEnabled() && content != null) { + LOGGER.trace(content.toString()); } boolean existed = existingConfiguration.exists(name); @@ -274,28 +268,21 @@ protected boolean hasPermissionsToCreate( } protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { - final String resourcename = request.param("name"); - final SecurityDynamicConfiguration configuration = load(getConfigName(), true); filter(configuration); - // no specific resource requested, return complete config if (resourcename == null || resourcename.length() == 0) { successResponse(channel, configuration); return; } - if (!configuration.exists(resourcename)) { notFound(channel, "Resource '" + resourcename + "' not found."); return; } - configuration.removeOthers(resourcename); successResponse(channel, configuration); - - return; } protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { @@ -305,15 +292,6 @@ protected final SecurityDynamicConfiguration load(final CType config, boolean return DynamicConfigFactory.addStatics(loaded); } - protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent, boolean acceptInvalid) { - SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex( - Collections.singleton(config), - logComplianceEvent, - acceptInvalid - ).get(config).deepClone(); - return DynamicConfigFactory.addStatics(loaded); - } - protected boolean ensureIndexExists() { if (!cs.state().metadata().hasConcreteIndex(this.securityIndexName)) { return false; @@ -434,7 +412,7 @@ protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClie // check if .opendistro_security index has been initialized if (!ensureIndexExists()) { - return channel -> internalErrorResponse(channel, ErrorType.SECURITY_NOT_INITIALIZED.getMessage()); + return channel -> internalErrorResponse(channel, RequestContentValidator.ValidationError.SECURITY_NOT_INITIALIZED.message()); } // check if request is authorized @@ -443,7 +421,7 @@ protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClie final User user = (User) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); final String userName = user == null ? null : user.getName(); if (authError != null) { - log.error("No permission to access REST API: " + authError); + LOGGER.error("No permission to access REST API: " + authError); auditLog.logMissingPrivileges(authError, userName, request); // for rest request request.params().clear(); @@ -465,7 +443,7 @@ protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClie handleApiRequest(channel, request, client); } catch (Exception e) { - log.error("Error processing request {}", request, e); + LOGGER.error("Error processing request {}", request, e); try { channel.sendResponse(new BytesRestResponse(channel, e)); } catch (IOException ioe) { @@ -475,37 +453,6 @@ protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClie }); } - protected boolean checkConfigUpdateResponse(final ConfigUpdateResponse response) { - - final int nodeCount = cs.state().getNodes().getNodes().size(); - final int expectedConfigCount = 1; - - boolean success = response.getNodes().size() == nodeCount; - if (!success) { - log.error("Expected " + nodeCount + " nodes to return response, but got only " + response.getNodes().size()); - } - - for (final String nodeId : response.getNodesMap().keySet()) { - final ConfigUpdateNodeResponse node = response.getNodesMap().get(nodeId); - final boolean successNode = node.getUpdatedConfigTypes() != null && node.getUpdatedConfigTypes().length == expectedConfigCount; - - if (!successNode) { - log.error( - "Expected " - + expectedConfigCount - + " config types for node " - + nodeId - + " but got only " - + Arrays.toString(node.getUpdatedConfigTypes()) - ); - } - - success = success && successNode; - } - - return success; - } - protected static XContentBuilder convertToJson(RestChannel channel, ToXContent toxContent) { try { XContentBuilder builder = channel.newBuilder(); @@ -541,12 +488,12 @@ protected void successResponse(RestChannel channel) { channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder)); } catch (IOException e) { internalErrorResponse(channel, "Unable to fetch license: " + e.getMessage()); - log.error("Cannot fetch convert license to XContent due to", e); + LOGGER.error("Cannot fetch convert license to XContent due to", e); } } - protected void badRequestResponse(RestChannel channel, AbstractConfigurationValidator validator) { - channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, validator.errorsAsXContent(channel))); + protected void badRequestResponse(RestChannel channel, ToXContent validationResult) { + channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, convertToJson(channel, validationResult))); } protected void successResponse(RestChannel channel, String message) { @@ -573,10 +520,6 @@ protected void internalErrorResponse(RestChannel channel, String message) { response(channel, RestStatus.INTERNAL_SERVER_ERROR, message); } - protected void unprocessable(RestChannel channel, String message) { - response(channel, RestStatus.UNPROCESSABLE_ENTITY, message); - } - protected void conflict(RestChannel channel, String message) { response(channel, RestStatus.CONFLICT, message); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index a93929f57b..4763d312dd 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -14,16 +14,20 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.generators.OpenBSDBCrypt; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; @@ -38,8 +42,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.AccountValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.Hashed; import org.opensearch.security.securityconf.impl.CType; @@ -58,6 +62,9 @@ * Currently this action serves GET and PUT request for /_opendistro/_security/api/account endpoint */ public class AccountApiAction extends AbstractApiAction { + + private final static Logger LOGGER = LogManager.getLogger(AccountApiAction.class); + private static final String RESOURCE_NAME = "account"; private static final List routes = addRoutesPrefix( ImmutableList.of(new Route(Method.GET, "/account"), new Route(Method.PUT, "/account")) @@ -154,7 +161,7 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception exception) { - log.error(exception.toString()); + LOGGER.error(exception.toString()); builder.startObject().field("error", exception.toString()).endObject(); @@ -241,9 +248,29 @@ public void onResponse(IndexResponse response) { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { + protected RequestContentValidator createValidator(final Object... params) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - return new AccountValidator(request, ref, this.settings, user.getName()); + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[] { user.getName() }; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Set mandatoryKeys() { + return ImmutableSet.of("current_password"); + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("hash", DataType.STRING, "password", DataType.STRING, "current_password", DataType.STRING); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java index 3b3e772eda..dee97dfa39 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java @@ -11,17 +11,12 @@ package org.opensearch.security.dlic.rest.api; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Set; - import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; - +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -32,14 +27,20 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.ActionGroupValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; + import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class ActionGroupsApiAction extends PatchableResourceApiAction { @@ -92,8 +93,35 @@ public List routes() { } @Override - protected AbstractConfigurationValidator getValidator(final RestRequest request, BytesReference ref, Object... param) { - return new ActionGroupValidator(request, isSuperAdmin(), ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + final ImmutableMap.Builder allowedKeys = ImmutableMap.builder(); + if (isSuperAdmin()) { + allowedKeys.put("reserved", DataType.BOOLEAN); + } + allowedKeys.put("allowed_actions", DataType.ARRAY); + allowedKeys.put("description", DataType.STRING); + allowedKeys.put("type", DataType.STRING); + return allowedKeys.build(); + } + + @Override + public Set mandatoryKeys() { + return ImmutableSet.of("allowed_actions"); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java index 054ac1ea46..a6b93f07dd 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java @@ -11,17 +11,12 @@ package org.opensearch.security.dlic.rest.api; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; - +import com.google.common.collect.ImmutableMap; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -31,8 +26,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.AllowlistValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -41,6 +36,11 @@ import org.opensearch.security.tools.SecurityAdmin; import org.opensearch.threadpool.ThreadPool; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + /** * This class implements GET and PUT operations to manage dynamic AllowlistingSettings. *

@@ -185,8 +185,23 @@ protected Endpoint getEndpoint() { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new AllowlistValidator(request, ref, this.settings, param); + protected RequestContentValidator createValidator(Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("enabled", DataType.BOOLEAN, "requests", DataType.OBJECT); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java index 078bfcc62a..61982eb296 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java @@ -11,19 +11,14 @@ package org.opensearch.security.dlic.rest.api; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; - +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.rest.RestChannel; @@ -32,18 +27,26 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auditlog.config.AuditConfig; +import org.opensearch.security.auditlog.impl.AuditCategory; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.configuration.StaticResourceException; import org.opensearch.security.dlic.rest.support.Utils; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.AuditValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; +import org.opensearch.security.dlic.rest.validation.ValidationResult; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; + import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; /** @@ -141,6 +144,62 @@ public class AuditApiAction extends PatchableResourceApiAction { private final PrivilegesEvaluator privilegesEvaluator; private final ThreadContext threadContext; + public static class AuditRequestContentValidator extends RequestContentValidator { + private static final Set DISABLED_REST_CATEGORIES = ImmutableSet.of( + AuditCategory.BAD_HEADERS, + AuditCategory.SSL_EXCEPTION, + AuditCategory.AUTHENTICATED, + AuditCategory.FAILED_LOGIN, + AuditCategory.GRANTED_PRIVILEGES, + AuditCategory.MISSING_PRIVILEGES + ); + + private static final Set DISABLED_TRANSPORT_CATEGORIES = ImmutableSet.of( + AuditCategory.BAD_HEADERS, + AuditCategory.SSL_EXCEPTION, + AuditCategory.AUTHENTICATED, + AuditCategory.FAILED_LOGIN, + AuditCategory.GRANTED_PRIVILEGES, + AuditCategory.MISSING_PRIVILEGES, + AuditCategory.INDEX_EVENT, + AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT + ); + + protected AuditRequestContentValidator(ValidationContext validationContext) { + super(validationContext); + } + + @Override + public ValidationResult validate(RestRequest request) throws IOException { + return super.validate(request).map(this::validateAuditPayload); + } + + @Override + public ValidationResult validate(RestRequest request, JsonNode jsonContent) throws IOException { + return super.validate(request, jsonContent).map(this::validateAuditPayload); + } + + private ValidationResult validateAuditPayload(final JsonNode jsonContent) { + try { + // try parsing to target type + final AuditConfig auditConfig = DefaultObjectMapper.readTree(jsonContent, AuditConfig.class); + final AuditConfig.Filter filter = auditConfig.getFilter(); + if (!DISABLED_REST_CATEGORIES.containsAll(filter.getDisabledRestCategories())) { + throw new IllegalArgumentException("Invalid REST categories passed in the request"); + } + if (!DISABLED_TRANSPORT_CATEGORIES.containsAll(filter.getDisabledTransportCategories())) { + throw new IllegalArgumentException("Invalid transport categories passed in the request"); + } + return ValidationResult.success(jsonContent); + } catch (final Exception e) { + // this.content is not valid json + this.validationError = ValidationError.BODY_NOT_PARSEABLE; + LOGGER.error("Invalid content passed in the request", e); + return ValidationResult.error(this); + } + } + } + public AuditApiAction( final Settings settings, final Path configPath, @@ -232,8 +291,23 @@ protected void handleDelete(RestChannel channel, final RestRequest request, fina } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { - return new AuditValidator(request, ref, this.settings, params); + protected RequestContentValidator createValidator(final Object... params) { + return new AuditRequestContentValidator(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("enabled", DataType.BOOLEAN, "audit", DataType.OBJECT, "compliance", DataType.OBJECT); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java index 87c82cf77c..dd9591f22e 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java @@ -20,7 +20,6 @@ import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -30,8 +29,7 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.NoOpValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -84,8 +82,8 @@ protected void handlePost(RestChannel channel, final RestRequest request, final } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); + protected RequestContentValidator createValidator(Object... params) { + return RequestContentValidator.NOOP_VALIDATOR; } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java index 4898897df7..ad7e035d7c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java @@ -18,10 +18,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.opensearch.action.ActionListener; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -34,8 +35,7 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.NoOpValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -45,6 +45,9 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class FlushCacheApiAction extends AbstractApiAction { + + private final static Logger LOGGER = LogManager.getLogger(FlushCacheApiAction.class); + private static final List routes = addRoutesPrefix( ImmutableList.of( new Route(Method.DELETE, "/cache"), @@ -101,19 +104,19 @@ protected void handleDelete(RestChannel channel, RestRequest request, Client cli @Override public void onResponse(ConfigUpdateResponse ur) { if (ur.hasFailures()) { - log.error("Cannot flush cache due to", ur.failures().get(0)); + LOGGER.error("Cannot flush cache due to", ur.failures().get(0)); internalErrorResponse(channel, "Cannot flush cache due to " + ur.failures().get(0).getMessage() + "."); return; } successResponse(channel, "Cache flushed successfully."); - if (log.isDebugEnabled()) { - log.debug("cache flushed successfully"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("cache flushed successfully"); } } @Override public void onFailure(Exception e) { - log.error("Cannot flush cache due to", e); + LOGGER.error("Cannot flush cache due to", e); internalErrorResponse(channel, "Cannot flush cache due to " + e.getMessage() + "."); } @@ -140,8 +143,8 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.NOOP_VALIDATOR; } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 6db8963ab5..201d599117 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -14,19 +14,19 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; -import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; @@ -35,8 +35,9 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.InternalUsersValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; +import org.opensearch.security.dlic.rest.validation.ValidationResult; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.Hashed; import org.opensearch.security.securityconf.impl.CType; @@ -268,33 +269,23 @@ protected void filter(SecurityDynamicConfiguration builder) { } @Override - protected AbstractConfigurationValidator postProcessApplyPatchResult( + protected ValidationResult postProcessApplyPatchResult( RestChannel channel, RestRequest request, JsonNode existingResourceAsJsonNode, JsonNode updatedResourceAsJsonNode, String resourceName - ) { - AbstractConfigurationValidator retVal = null; + ) throws IOException { + RequestContentValidator retVal = null; JsonNode passwordNode = updatedResourceAsJsonNode.get("password"); - if (passwordNode != null) { String plainTextPassword = passwordNode.asText(); - try { - XContentBuilder builder = channel.newBuilder(); - builder.startObject(); - builder.field("password", plainTextPassword); - builder.endObject(); - retVal = getValidator(request, BytesReference.bytes(builder), resourceName); - } catch (IOException e) { - log.error(e.toString()); - } - + final JsonNode passwordObject = DefaultObjectMapper.objectMapper.createObjectNode().put("password", plainTextPassword); + final ValidationResult validationResult = createValidator(resourceName).validate(request, passwordObject); ((ObjectNode) updatedResourceAsJsonNode).remove("password"); ((ObjectNode) updatedResourceAsJsonNode).set("hash", new TextNode(hash(plainTextPassword.toCharArray()))); - return retVal; + return validationResult; } - return null; } @@ -309,7 +300,32 @@ protected CType getConfigName() { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { - return new InternalUsersValidator(request, isSuperAdmin(), ref, this.settings, params); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + final ImmutableMap.Builder allowedKeys = ImmutableMap.builder(); + if (isSuperAdmin()) { + allowedKeys.put("reserved", DataType.BOOLEAN); + } + return allowedKeys.put("backend_roles", DataType.ARRAY) + .put("attributes", DataType.OBJECT) + .put("description", DataType.STRING) + .put("opendistro_security_roles", DataType.ARRAY) + .put("hash", DataType.STRING) + .put("password", DataType.STRING) + .build(); + } + }); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index ff82af9426..73066666b9 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.opensearch.action.ActionListener; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.bulk.BulkRequestBuilder; @@ -45,8 +47,7 @@ import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.NoOpValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.Migration; import org.opensearch.security.securityconf.impl.CType; @@ -71,6 +72,8 @@ // CS-ENFORCE-SINGLE public class MigrateApiAction extends AbstractApiAction { + private final static Logger LOGGER = LogManager.getLogger(MigrateApiAction.class); + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.POST, "/migrate"))); @Inject @@ -187,7 +190,7 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien public void onResponse(AcknowledgedResponse response) { if (response.isAcknowledged()) { - log.debug("opendistro_security index deleted successfully"); + LOGGER.debug("opendistro_security index deleted successfully"); client.admin() .indices() @@ -215,7 +218,7 @@ public void onResponse(CreateIndexResponse response) { cTypes.add(id); } } catch (final IOException e1) { - log.error("Unable to create bulk request " + e1, e1); + LOGGER.error("Unable to create bulk request " + e1, e1); internalErrorResponse(channel, "Unable to create bulk request."); return; } @@ -229,7 +232,7 @@ public void onResponse(CreateIndexResponse response) { @Override public void onResponse(BulkResponse response) { if (response.hasFailures()) { - log.error( + LOGGER.error( "Unable to upload migrated configuration because of " + response.buildFailureMessage() ); @@ -238,7 +241,7 @@ public void onResponse(BulkResponse response) { "Unable to upload migrated configuration (bulk index failed)." ); } else { - log.debug("Migration completed"); + LOGGER.debug("Migration completed"); successResponse(channel, "Migration completed."); } @@ -246,7 +249,7 @@ public void onResponse(BulkResponse response) { @Override public void onFailure(Exception e) { - log.error("Unable to upload migrated configuration because of " + e, e); + LOGGER.error("Unable to upload migrated configuration because of " + e, e); internalErrorResponse(channel, "Unable to upload migrated configuration."); } } @@ -257,19 +260,19 @@ public void onFailure(Exception e) { @Override public void onFailure(Exception e) { - log.error("Unable to create opendistro_security index because of " + e, e); + LOGGER.error("Unable to create opendistro_security index because of " + e, e); internalErrorResponse(channel, "Unable to create opendistro_security index."); } }); } else { - log.error("Unable to create opendistro_security index."); + LOGGER.error("Unable to create opendistro_security index."); } } @Override public void onFailure(Exception e) { - log.error("Unable to delete opendistro_security index because of " + e, e); + LOGGER.error("Unable to delete opendistro_security index because of " + e, e); internalErrorResponse(channel, "Unable to delete opendistro_security index."); } }); @@ -295,8 +298,8 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.NOOP_VALIDATOR; } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java index bef77ecb2b..4e36101692 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -21,12 +22,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BytesRestResponse; @@ -37,8 +40,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.MultiTenancyConfigValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -53,6 +56,12 @@ public class MultiTenancyConfigApiAction extends AbstractApiAction { + private final static Logger LOGGER = LogManager.getLogger(MultiTenancyConfigApiAction.class); + + public static final String DEFAULT_TENANT_JSON_PROPERTY = "default_tenant"; + public static final String PRIVATE_TENANT_ENABLED_JSON_PROPERTY = "private_tenant_enabled"; + public static final String MULTITENANCY_ENABLED_JSON_PROPERTY = "multitenancy_enabled"; + private static final List ROUTES = addRoutesPrefix( ImmutableList.of(new Route(GET, "/tenancy/config"), new Route(PUT, "/tenancy/config")) ); @@ -90,8 +99,30 @@ public MultiTenancyConfigApiAction( } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { - return new MultiTenancyConfigValidator(request, ref, settings, params); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of( + DEFAULT_TENANT_JSON_PROPERTY, + DataType.STRING, + PRIVATE_TENANT_ENABLED_JSON_PROPERTY, + DataType.BOOLEAN, + MULTITENANCY_ENABLED_JSON_PROPERTY, + DataType.BOOLEAN + ); + } + }); } @Override @@ -121,18 +152,15 @@ private void multitenancyResponse(final ConfigV7 config, final RestChannel chann new BytesRestResponse( RestStatus.OK, contentBuilder.startObject() - .field(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY, config.dynamic.kibana.default_tenant) - .field( - MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY, - config.dynamic.kibana.private_tenant_enabled - ) - .field(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY, config.dynamic.kibana.multitenancy_enabled) + .field(DEFAULT_TENANT_JSON_PROPERTY, config.dynamic.kibana.default_tenant) + .field(PRIVATE_TENANT_ENABLED_JSON_PROPERTY, config.dynamic.kibana.private_tenant_enabled) + .field(MULTITENANCY_ENABLED_JSON_PROPERTY, config.dynamic.kibana.multitenancy_enabled) .endObject() ) ); } catch (final Exception e) { internalErrorResponse(channel, e.getMessage()); - log.error("Error handle request ", e); + LOGGER.error("Error handle request ", e); } } @@ -163,18 +191,14 @@ public void onResponse(IndexResponse response) { } private void updateAndValidatesValues(final ConfigV7 config, final JsonNode jsonContent) { - if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY))) { - config.dynamic.kibana.default_tenant = jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY).asText(); + if (Objects.nonNull(jsonContent.findValue(DEFAULT_TENANT_JSON_PROPERTY))) { + config.dynamic.kibana.default_tenant = jsonContent.findValue(DEFAULT_TENANT_JSON_PROPERTY).asText(); } - if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY))) { - config.dynamic.kibana.private_tenant_enabled = jsonContent.findValue( - MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY - ).booleanValue(); + if (Objects.nonNull(jsonContent.findValue(PRIVATE_TENANT_ENABLED_JSON_PROPERTY))) { + config.dynamic.kibana.private_tenant_enabled = jsonContent.findValue(PRIVATE_TENANT_ENABLED_JSON_PROPERTY).booleanValue(); } - if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY))) { - config.dynamic.kibana.multitenancy_enabled = jsonContent.findValue( - MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY - ).asBoolean(); + if (Objects.nonNull(jsonContent.findValue(MULTITENANCY_ENABLED_JSON_PROPERTY))) { + config.dynamic.kibana.multitenancy_enabled = jsonContent.findValue(MULTITENANCY_ENABLED_JSON_PROPERTY).asBoolean(); } final String defaultTenant = Optional.ofNullable(config.dynamic.kibana.default_tenant).map(String::toLowerCase).orElse(""); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java index c7392e4441..8d09705799 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java @@ -15,13 +15,16 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -31,8 +34,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.NodesDnValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.NodesDn; @@ -180,7 +183,27 @@ protected CType getConfigName() { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { - return new NodesDnValidator(request, ref, this.settings, params); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Set mandatoryKeys() { + return ImmutableSet.of("nodes_dn"); + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("nodes_dn", DataType.ARRAY); + } + }); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java index f630a6bab7..401110c9b1 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java @@ -12,12 +12,10 @@ package org.opensearch.security.dlic.rest.api; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Iterator; import java.util.List; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.flipkart.zjsonpatch.JsonPatch; @@ -28,8 +26,6 @@ import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesArray; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.common.Strings; @@ -42,7 +38,8 @@ import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.dlic.rest.support.Utils; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.ValidationResult; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -140,7 +137,7 @@ private void handleSinglePatch( return; } - AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult( + ValidationResult originalValidationResult = postProcessApplyPatchResult( channel, request, existingResourceAsJsonNode, @@ -148,10 +145,10 @@ private void handleSinglePatch( name ); - if (originalValidator != null) { - if (!originalValidator.validate()) { + if (originalValidationResult != null) { + if (!originalValidationResult.isValid()) { request.params().clear(); - badRequestResponse(channel, originalValidator); + badRequestResponse(channel, originalValidationResult.errorMessage()); return; } } @@ -162,9 +159,9 @@ private void handleSinglePatch( return; } - AbstractConfigurationValidator validator = getValidator(request, patchedResourceAsJsonNode); - - if (!validator.validate()) { + RequestContentValidator validator = createValidator(); + final ValidationResult validationResult = validator.validate(request, patchedResourceAsJsonNode); + if (!validationResult.isValid()) { request.params().clear(); badRequestResponse(channel, validator); return; @@ -230,7 +227,7 @@ private void handleBulkPatch( JsonNode oldResource = existingAsObjectNode.get(resourceName); JsonNode patchedResource = patchedAsJsonNode.get(resourceName); - AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult( + ValidationResult originalValidationResult = postProcessApplyPatchResult( channel, request, oldResource, @@ -238,10 +235,10 @@ private void handleBulkPatch( resourceName ); - if (originalValidator != null) { - if (!originalValidator.validate()) { + if (originalValidationResult != null) { + if (!originalValidationResult.isValid()) { request.params().clear(); - badRequestResponse(channel, originalValidator); + badRequestResponse(channel, originalValidationResult.errorMessage()); return; } } @@ -253,9 +250,9 @@ private void handleBulkPatch( } if (oldResource == null || !oldResource.equals(patchedResource)) { - AbstractConfigurationValidator validator = getValidator(request, patchedResource); - - if (!validator.validate()) { + RequestContentValidator validator = createValidator(); + final ValidationResult validationResult = validator.validate(request, patchedResource); + if (!validationResult.isValid()) { request.params().clear(); badRequestResponse(channel, validator); return; @@ -299,13 +296,13 @@ private JsonNode applyPatch(JsonNode jsonPatch, JsonNode existingResourceAsJsonN return JsonPatch.apply(jsonPatch, existingResourceAsJsonNode); } - protected AbstractConfigurationValidator postProcessApplyPatchResult( + protected ValidationResult postProcessApplyPatchResult( RestChannel channel, RestRequest request, JsonNode existingResourceAsJsonNode, JsonNode updatedResourceAsJsonNode, String resourceName - ) { + ) throws IOException { // do nothing by default return null; } @@ -320,13 +317,6 @@ protected void handleApiRequest(RestChannel channel, final RestRequest request, } } - private AbstractConfigurationValidator getValidator(RestRequest request, JsonNode patchedResource) throws JsonProcessingException { - BytesReference patchedResourceAsByteReference = new BytesArray( - DefaultObjectMapper.objectMapper.writeValueAsString(patchedResource).getBytes(StandardCharsets.UTF_8) - ); - return getValidator(request, patchedResourceAsByteReference); - } - // Prevent the case where action group references to itself in the allowed_actions. protected Boolean hasActionGroupSelfReference(SecurityDynamicConfiguration mdc, String name) { List allowedActions = ((ActionGroupsV7) mdc.getCEntry(name)).getAllowed_actions(); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java index ea1ca791ba..5523bc08a7 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java @@ -11,15 +11,13 @@ package org.opensearch.security.dlic.rest.api; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; - +import com.google.common.collect.ImmutableMap; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.ReadContext; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestController; @@ -28,14 +26,22 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.RolesValidator; +import org.opensearch.security.configuration.MaskedField; +import org.opensearch.security.configuration.Salt; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; +import org.opensearch.security.dlic.rest.validation.ValidationResult; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.threadpool.ThreadPool; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class RolesApiAction extends PatchableResourceApiAction { @@ -50,6 +56,50 @@ public class RolesApiAction extends PatchableResourceApiAction { ) ); + public static class RoleValidator extends RequestContentValidator { + + private static final Salt SALT = new Salt(new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6 }); + + protected RoleValidator(ValidationContext validationContext) { + super(validationContext); + } + + @Override + public ValidationResult validate(RestRequest request) throws IOException { + return super.validate(request).map(this::validateMaskedFields); + } + + @Override + public ValidationResult validate(RestRequest request, JsonNode jsonContent) throws IOException { + return super.validate(request, jsonContent).map(this::validateMaskedFields); + } + + private ValidationResult validateMaskedFields(final JsonNode content) { + final ReadContext ctx = JsonPath.parse(content.toString()); + final List maskedFields = ctx.read("$..masked_fields[*]"); + if (maskedFields != null) { + for (String mf : maskedFields) { + if (!validateMaskedFieldSyntax(mf)) { + this.validationError = ValidationError.WRONG_DATATYPE; + return ValidationResult.error(this); + } + } + } + return ValidationResult.success(content); + } + + private boolean validateMaskedFieldSyntax(String mf) { + try { + new MaskedField(mf, SALT).isValid(); + } catch (Exception e) { + wrongDataTypes.put("Masked field not valid: " + mf, e.getMessage()); + return false; + } + return true; + } + + } + @Inject public RolesApiAction( Settings settings, @@ -78,8 +128,29 @@ protected Endpoint getEndpoint() { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new RolesValidator(request, isSuperAdmin(), ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return new RoleValidator(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + final ImmutableMap.Builder allowedKeys = ImmutableMap.builder(); + if (isSuperAdmin()) allowedKeys.put("reserved", DataType.BOOLEAN); + return allowedKeys.put("cluster_permissions", DataType.ARRAY) + .put("tenant_permissions", DataType.ARRAY) + .put("index_permissions", DataType.ARRAY) + .put("description", DataType.STRING) + .build(); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java index e2909edd6c..7fba7e897c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java @@ -14,14 +14,17 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Map; +import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -32,8 +35,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.RolesMappingValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -150,8 +153,35 @@ protected Endpoint getEndpoint() { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new RolesMappingValidator(request, isSuperAdmin(), ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Set mandatoryOrKeys() { + return ImmutableSet.of("backend_roles", "and_backend_roles", "hosts", "users"); + } + + @Override + public Map allowedKeys() { + final ImmutableMap.Builder allowedKeys = ImmutableMap.builder(); + if (isSuperAdmin()) allowedKeys.put("reserved", DataType.BOOLEAN); + return allowedKeys.put("backend_roles", DataType.ARRAY) + .put("and_backend_roles", DataType.ARRAY) + .put("hosts", DataType.ARRAY) + .put("users", DataType.ARRAY) + .put("description", DataType.STRING) + .build(); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java index c4abc45cee..963d64a4df 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java @@ -15,13 +15,14 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestChannel; @@ -31,8 +32,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.SecurityConfigValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -134,8 +135,23 @@ protected void handleDelete(RestChannel channel, final RestRequest request, fina } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new SecurityConfigValidator(request, ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("dynamic", DataType.OBJECT); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java index 0b576bfd68..0e454eb8f6 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java @@ -27,7 +27,6 @@ import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.Settings; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.rest.BytesRestResponse; @@ -39,7 +38,7 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -306,7 +305,7 @@ public String getName() { } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... params) { + protected RequestContentValidator createValidator(final Object... params) { return null; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java index b011325c86..f0c9be3ee9 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java @@ -29,12 +29,13 @@ import java.nio.file.Path; import java.util.List; +import java.util.Map; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.rest.RestController; @@ -43,8 +44,8 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.TenantValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -102,8 +103,27 @@ protected Endpoint getEndpoint() { } @Override - protected AbstractConfigurationValidator getValidator(final RestRequest request, BytesReference ref, Object... param) { - return new TenantValidator(request, isSuperAdmin(), ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return params; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public Map allowedKeys() { + final ImmutableMap.Builder allowedKeys = ImmutableMap.builder(); + if (isSuperAdmin()) { + allowedKeys.put("reserved", DataType.BOOLEAN); + } + return allowedKeys.put("description", DataType.STRING).build(); + } + }); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java index f9612d3b40..f3f882cc19 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java @@ -20,7 +20,6 @@ import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.collect.Tuple; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; @@ -32,9 +31,9 @@ import org.opensearch.security.auditlog.config.AuditConfig; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; -import org.opensearch.security.dlic.rest.validation.NoOpValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.Migration; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; @@ -166,8 +165,8 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); + protected RequestContentValidator createValidator(final Object... params) { + return RequestContentValidator.NOOP_VALIDATOR; } @Override @@ -186,4 +185,13 @@ protected void consumeParameters(final RestRequest request) { request.paramAsBoolean("accept_invalid", false); } + private final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent, boolean acceptInvalid) { + SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex( + Collections.singleton(config), + logComplianceEvent, + acceptInvalid + ).get(config).deepClone(); + return DynamicConfigFactory.addStatics(loaded); + } + } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java deleted file mode 100644 index 543ad4b4a7..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.rest.RestChannel; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.RestRequest.Method; -import org.opensearch.security.DefaultObjectMapper; -import org.opensearch.security.support.ConfigConstants; - -public abstract class AbstractConfigurationValidator { - - JsonFactory factory = new JsonFactory(); - - /* public for testing */ - public final static String INVALID_KEYS_KEY = "invalid_keys"; - - /* public for testing */ - public final static String MISSING_MANDATORY_KEYS_KEY = "missing_mandatory_keys"; - - /* public for testing */ - public final static String MISSING_MANDATORY_OR_KEYS_KEY = "specify_one_of"; - - protected final Logger log = LogManager.getLogger(this.getClass()); - - /** Define the various keys for this validator */ - protected final Map allowedKeys = new HashMap<>(); - - protected final Set mandatoryKeys = new HashSet<>(); - - protected final Set mandatoryOrKeys = new HashSet<>(); - - protected final Map wrongDatatypes = new HashMap<>(); - - /** Contain errorneous keys */ - protected final Set missingMandatoryKeys = new HashSet<>(); - - protected final Set invalidKeys = new HashSet<>(); - - protected final Set missingMandatoryOrKeys = new HashSet<>(); - - /** The error type */ - protected ErrorType errorType = ErrorType.NONE; - - /** Behaviour regarding payload */ - protected boolean payloadMandatory = false; - - protected boolean payloadAllowed = true; - - protected final Method method; - - protected final BytesReference content; - - protected final Settings opensearchSettings; - - protected final RestRequest request; - - protected final Object[] param; - - private JsonNode contentAsNode; - - public AbstractConfigurationValidator( - final RestRequest request, - final BytesReference ref, - final Settings opensearchSettings, - Object... param - ) { - this.content = ref; - this.method = request.method(); - this.opensearchSettings = opensearchSettings; - this.request = request; - this.param = param; - } - - public JsonNode getContentAsNode() { - return contentAsNode; - } - - /** - * - * @return false if validation fails - */ - public boolean validate() { - // no payload for DELETE and GET requests - if (method.equals(Method.DELETE) || method.equals(Method.GET)) { - return true; - } - - if (this.payloadMandatory && content.length() == 0) { - this.errorType = ErrorType.PAYLOAD_MANDATORY; - return false; - } - - if (!this.payloadMandatory && content.length() == 0) { - return true; - } - - if (this.payloadMandatory && content.length() > 0) { - - try { - if (DefaultObjectMapper.readTree(content.utf8ToString()).size() == 0) { - this.errorType = ErrorType.PAYLOAD_MANDATORY; - return false; - } - - } catch (IOException e) { - log.error(errorType.BODY_NOT_PARSEABLE.toString(), e); - this.errorType = ErrorType.BODY_NOT_PARSEABLE; - return false; - } - } - - if (!this.payloadAllowed && content.length() > 0) { - this.errorType = ErrorType.PAYLOAD_NOT_ALLOWED; - return false; - } - - // try to parse payload - Set requested = new HashSet(); - try { - contentAsNode = DefaultObjectMapper.readTree(content.utf8ToString()); - requested.addAll(ImmutableList.copyOf(contentAsNode.fieldNames())); - } catch (Exception e) { - log.error(errorType.BODY_NOT_PARSEABLE.toString(), e); - this.errorType = ErrorType.BODY_NOT_PARSEABLE; - return false; - } - - // mandatory settings, one of ... - if (Collections.disjoint(requested, mandatoryOrKeys)) { - this.missingMandatoryOrKeys.addAll(mandatoryOrKeys); - } - - // mandatory settings - Set mandatory = new HashSet<>(mandatoryKeys); - mandatory.removeAll(requested); - missingMandatoryKeys.addAll(mandatory); - - // invalid settings - Set allowed = new HashSet<>(allowedKeys.keySet()); - requested.removeAll(allowed); - this.invalidKeys.addAll(requested); - boolean valid = missingMandatoryKeys.isEmpty() && invalidKeys.isEmpty() && missingMandatoryOrKeys.isEmpty(); - if (!valid) { - this.errorType = ErrorType.INVALID_CONFIGURATION; - } - - // check types - try { - if (!checkDatatypes()) { - this.errorType = ErrorType.WRONG_DATATYPE; - return false; - } - } catch (Exception e) { - log.error(errorType.BODY_NOT_PARSEABLE.toString(), e); - this.errorType = ErrorType.BODY_NOT_PARSEABLE; - return false; - } - - // null element in the values of all the possible keys with DataType as ARRAY - for (Entry allowedKey : allowedKeys.entrySet()) { - JsonNode value = contentAsNode.get(allowedKey.getKey()); - if (value != null) { - if (hasNullArrayElement(value)) { - this.errorType = ErrorType.NULL_ARRAY_ELEMENT; - return false; - } - } - } - return valid; - } - - private boolean checkDatatypes() throws Exception { - String contentAsJson = XContentHelper.convertToJson(content, false, XContentType.JSON); - try (JsonParser parser = factory.createParser(contentAsJson)) { - JsonToken token = null; - while ((token = parser.nextToken()) != null) { - if (token.equals(JsonToken.FIELD_NAME)) { - String currentName = parser.getCurrentName(); - DataType dataType = allowedKeys.get(currentName); - if (dataType != null) { - JsonToken valueToken = parser.nextToken(); - switch (dataType) { - case STRING: - if (!valueToken.equals(JsonToken.VALUE_STRING)) { - wrongDatatypes.put(currentName, "String expected"); - } - break; - case ARRAY: - if (!valueToken.equals(JsonToken.START_ARRAY) && !valueToken.equals(JsonToken.END_ARRAY)) { - wrongDatatypes.put(currentName, "Array expected"); - } - break; - case OBJECT: - if (!valueToken.equals(JsonToken.START_OBJECT) && !valueToken.equals(JsonToken.END_OBJECT)) { - wrongDatatypes.put(currentName, "Object expected"); - } - break; - } - } - } - } - return wrongDatatypes.isEmpty(); - } - } - - public XContentBuilder errorsAsXContent(RestChannel channel) { - try { - final XContentBuilder builder = channel.newBuilder(); - builder.startObject(); - switch (this.errorType) { - case NONE: - builder.field("status", "error"); - builder.field("reason", errorType.getMessage()); - break; - case INVALID_CONFIGURATION: - builder.field("status", "error"); - builder.field("reason", ErrorType.INVALID_CONFIGURATION.getMessage()); - addErrorMessage(builder, INVALID_KEYS_KEY, invalidKeys); - addErrorMessage(builder, MISSING_MANDATORY_KEYS_KEY, missingMandatoryKeys); - addErrorMessage(builder, MISSING_MANDATORY_OR_KEYS_KEY, missingMandatoryKeys); - break; - case INVALID_PASSWORD: - builder.field("status", "error"); - builder.field( - "reason", - opensearchSettings.get( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, - "Password does not match minimum criteria" - ) - ); - break; - case WEAK_PASSWORD: - case SIMILAR_PASSWORD: - builder.field("status", "error"); - builder.field( - "reason", - opensearchSettings.get(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, errorType.message) - ); - break; - case WRONG_DATATYPE: - builder.field("status", "error"); - builder.field("reason", ErrorType.WRONG_DATATYPE.getMessage()); - for (Entry entry : wrongDatatypes.entrySet()) { - builder.field(entry.getKey(), entry.getValue()); - } - break; - case NULL_ARRAY_ELEMENT: - builder.field("status", "error"); - builder.field("reason", ErrorType.NULL_ARRAY_ELEMENT.getMessage()); - break; - default: - builder.field("status", "error"); - builder.field("reason", errorType.getMessage()); - - } - builder.endObject(); - return builder; - } catch (IOException ex) { - log.error("Cannot build error settings", ex); - return null; - } - } - - private void addErrorMessage(final XContentBuilder builder, final String message, final Set keys) throws IOException { - if (!keys.isEmpty()) { - builder.startObject(message); - builder.field("keys", Joiner.on(",").join(keys.toArray(new String[0]))); - builder.endObject(); - } - } - - public static enum DataType { - STRING, - ARRAY, - OBJECT, - BOOLEAN; - } - - public static enum ErrorType { - NONE("ok"), - INVALID_CONFIGURATION("Invalid configuration"), - INVALID_PASSWORD("Invalid password"), - WEAK_PASSWORD("Weak password"), - SIMILAR_PASSWORD("Password is similar to user name"), - WRONG_DATATYPE("Wrong datatype"), - BODY_NOT_PARSEABLE("Could not parse content of request."), - PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."), - PAYLOAD_MANDATORY("Request body required for this action."), - SECURITY_NOT_INITIALIZED("Security index not initialized"), - NULL_ARRAY_ELEMENT("`null` is not allowed as json array element"); - - private String message; - - private ErrorType(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - } - - protected final boolean hasParams() { - return param != null && param.length > 0; - } - - private boolean hasNullArrayElement(JsonNode node) { - for (JsonNode element : node) { - if (element.isNull()) { - if (node.isArray()) { - return true; - } - } else if (element.isContainerNode()) { - if (hasNullArrayElement(element)) { - return true; - } - } - } - return false; - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AccountValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AccountValidator.java deleted file mode 100644 index a2f085f05b..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AccountValidator.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -/** - * Validator for Account Api Action. - */ -public class AccountValidator extends CredentialsValidator { - public AccountValidator(RestRequest request, BytesReference ref, Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - allowedKeys.put("current_password", DataType.STRING); - mandatoryKeys.add("current_password"); - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java deleted file mode 100644 index 7c65ff4567..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class ActionGroupValidator extends AbstractConfigurationValidator { - - public ActionGroupValidator( - final RestRequest request, - boolean isSuperAdmin, - BytesReference ref, - final Settings opensearchSettings, - Object... param - ) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("allowed_actions", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - allowedKeys.put("type", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - - mandatoryKeys.add("allowed_actions"); - } - -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AllowlistValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AllowlistValidator.java deleted file mode 100644 index 9fc7465642..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AllowlistValidator.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class AllowlistValidator extends AbstractConfigurationValidator { - - public AllowlistValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("enabled", DataType.BOOLEAN); - allowedKeys.put("requests", DataType.OBJECT); - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java deleted file mode 100644 index b57cd1e715..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import java.util.Set; - -import com.google.common.collect.ImmutableSet; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; -import org.opensearch.security.DefaultObjectMapper; -import org.opensearch.security.auditlog.config.AuditConfig; -import org.opensearch.security.auditlog.impl.AuditCategory; - -public class AuditValidator extends AbstractConfigurationValidator { - - private static final Set DISABLED_REST_CATEGORIES = ImmutableSet.of( - AuditCategory.BAD_HEADERS, - AuditCategory.SSL_EXCEPTION, - AuditCategory.AUTHENTICATED, - AuditCategory.FAILED_LOGIN, - AuditCategory.GRANTED_PRIVILEGES, - AuditCategory.MISSING_PRIVILEGES - ); - - private static final Set DISABLED_TRANSPORT_CATEGORIES = ImmutableSet.of( - AuditCategory.BAD_HEADERS, - AuditCategory.SSL_EXCEPTION, - AuditCategory.AUTHENTICATED, - AuditCategory.FAILED_LOGIN, - AuditCategory.GRANTED_PRIVILEGES, - AuditCategory.MISSING_PRIVILEGES, - AuditCategory.INDEX_EVENT, - AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT - ); - - public AuditValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, final Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - this.allowedKeys.put("enabled", DataType.BOOLEAN); - this.allowedKeys.put("audit", DataType.OBJECT); - this.allowedKeys.put("compliance", DataType.OBJECT); - } - - @Override - public boolean validate() { - if (!super.validate()) { - return false; - } - - if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) - && this.content != null - && this.content.length() > 0) { - try { - // try parsing to target type - final AuditConfig auditConfig = DefaultObjectMapper.readTree(getContentAsNode(), AuditConfig.class); - final AuditConfig.Filter filter = auditConfig.getFilter(); - if (!DISABLED_REST_CATEGORIES.containsAll(filter.getDisabledRestCategories())) { - throw new IllegalArgumentException("Invalid REST categories passed in the request"); - } - if (!DISABLED_TRANSPORT_CATEGORIES.containsAll(filter.getDisabledTransportCategories())) { - throw new IllegalArgumentException("Invalid transport categories passed in the request"); - } - } catch (Exception e) { - // this.content is not valid json - this.errorType = ErrorType.BODY_NOT_PARSEABLE; - log.error("Invalid content passed in the request", e); - return false; - } - } - return true; - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java deleted file mode 100644 index 283af8dd00..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import java.util.Map; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.core.common.compress.NotXContentException; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.core.common.Strings; -import org.opensearch.rest.RestRequest; -import org.opensearch.security.ssl.util.Utils; - -/** - * Validator for validating password and hash present in the payload - */ -public class CredentialsValidator extends AbstractConfigurationValidator { - - private final PasswordValidator passwordValidator; - - public CredentialsValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - this.passwordValidator = PasswordValidator.of(opensearchSettings); - allowedKeys.put("hash", DataType.STRING); - allowedKeys.put("password", DataType.STRING); - } - - /** - * Function to validate password in the content body. - * @return true if validation is successful else false - */ - @Override - public boolean validate() { - if (!super.validate()) { - return false; - } - if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) - && this.content != null - && this.content.length() > 1) { - try { - final Map contentAsMap = XContentHelper.convertToMap(this.content, false, XContentType.JSON).v2(); - final String password = (String) contentAsMap.get("password"); - if (password != null) { - // Password is not allowed to be empty if present. - if (password.isEmpty()) { - this.errorType = ErrorType.INVALID_PASSWORD; - return false; - } - final String username = Utils.coalesce(request.param("name"), hasParams() ? (String) param[0] : null); - if (Strings.isNullOrEmpty(username)) { - if (log.isDebugEnabled()) { - log.debug("Unable to validate username because no user is given"); - } - return false; - } - final ErrorType passwordValidationResult = passwordValidator.validate(username, password); - if (passwordValidationResult != ErrorType.NONE) { - this.errorType = passwordValidationResult; - return false; - } - } - } catch (NotXContentException e) { - // this.content is not valid json/yaml - log.error("Invalid xContent: " + e, e); - return false; - } - } - return true; - } - -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java deleted file mode 100644 index 87423e3912..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -/** - * Validator for Internal Users Api Action. - */ -public class InternalUsersValidator extends CredentialsValidator { - - public InternalUsersValidator( - final RestRequest request, - boolean isSuperAdmin, - BytesReference ref, - final Settings opensearchSettings, - Object... param - ) { - super(request, ref, opensearchSettings, param); - allowedKeys.put("backend_roles", DataType.ARRAY); - allowedKeys.put("attributes", DataType.OBJECT); - allowedKeys.put("description", DataType.STRING); - allowedKeys.put("opendistro_security_roles", DataType.ARRAY); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java deleted file mode 100644 index dd07e9ac2e..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class MultiTenancyConfigValidator extends AbstractConfigurationValidator { - - public static final String DEFAULT_TENANT_JSON_PROPERTY = "default_tenant"; - public static final String PRIVATE_TENANT_ENABLED_JSON_PROPERTY = "private_tenant_enabled"; - public static final String MULTITENANCY_ENABLED_JSON_PROPERTY = "multitenancy_enabled"; - - public MultiTenancyConfigValidator(RestRequest request, BytesReference ref, Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put(DEFAULT_TENANT_JSON_PROPERTY, DataType.STRING); - allowedKeys.put(PRIVATE_TENANT_ENABLED_JSON_PROPERTY, DataType.BOOLEAN); - allowedKeys.put(MULTITENANCY_ENABLED_JSON_PROPERTY, DataType.BOOLEAN); - } - -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java deleted file mode 100644 index d29ec91561..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class NoOpValidator extends AbstractConfigurationValidator { - - public NoOpValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - } - -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/NodesDnValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/NodesDnValidator.java deleted file mode 100644 index a2abb06f7b..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/NodesDnValidator.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class NodesDnValidator extends AbstractConfigurationValidator { - - public NodesDnValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - - allowedKeys.put("nodes_dn", DataType.ARRAY); - mandatoryKeys.add("nodes_dn"); - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java index ac521dee8a..767f823e45 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java @@ -11,23 +11,21 @@ package org.opensearch.security.dlic.rest.validation; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.function.Predicate; -import java.util.regex.Pattern; - import com.google.common.collect.ImmutableList; import com.nulabinc.zxcvbn.Strength; import com.nulabinc.zxcvbn.Zxcvbn; import com.nulabinc.zxcvbn.matchers.Match; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; - import org.opensearch.common.settings.Settings; import org.opensearch.core.common.Strings; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator.ErrorType; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.function.Predicate; +import java.util.regex.Pattern; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH; @@ -79,14 +77,14 @@ public static PasswordValidator of(final Settings settings) { ); } - ErrorType validate(final String username, final String password) { + public RequestContentValidator.ValidationError validate(final String username, final String password) { if (minPasswordLength > 0 && password.length() < minPasswordLength) { logger.debug( "Password is too short, the minimum required length is {}, but current length is {}", minPasswordLength, password.length() ); - return ErrorType.INVALID_PASSWORD; + return RequestContentValidator.ValidationError.INVALID_PASSWORD; } if (password.length() > MAX_LENGTH) { logger.debug( @@ -94,11 +92,11 @@ ErrorType validate(final String username, final String password) { MAX_LENGTH, password.length() ); - return ErrorType.INVALID_PASSWORD; + return RequestContentValidator.ValidationError.INVALID_PASSWORD; } if (Objects.nonNull(passwordRegexpPattern) && !passwordRegexpPattern.matcher(password).matches()) { logger.debug("Regex does not match password"); - return ErrorType.INVALID_PASSWORD; + return RequestContentValidator.ValidationError.INVALID_PASSWORD; } final Strength strength = zxcvbn.measure(password, ImmutableList.of(username)); if (strength.getScore() < scoreStrength.score()) { @@ -107,14 +105,14 @@ ErrorType validate(final String username, final String password) { scoreStrength, ScoreStrength.fromScore(strength.getScore()) ); - return ErrorType.WEAK_PASSWORD; + return RequestContentValidator.ValidationError.WEAK_PASSWORD; } final boolean similar = strength.getSequence().stream().anyMatch(USERNAME_SIMILARITY_CHECK); if (similar) { logger.debug("Password is too similar to the user name {}", username); - return ErrorType.SIMILAR_PASSWORD; + return RequestContentValidator.ValidationError.SIMILAR_PASSWORD; } - return ErrorType.NONE; + return RequestContentValidator.ValidationError.NONE; } public enum ScoreStrength { diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java new file mode 100644 index 0000000000..4c1ef559fd --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/RequestContentValidator.java @@ -0,0 +1,382 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.validation; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.Strings; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.rest.RestRequest; +import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.ssl.util.Utils; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE; + +public class RequestContentValidator implements ToXContent { + + public final static RequestContentValidator NOOP_VALIDATOR = new RequestContentValidator(new ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Map allowedKeys() { + return Collections.emptyMap(); + } + }) { + @Override + public ValidationResult validate(RestRequest request) { + return ValidationResult.success(DefaultObjectMapper.objectMapper.createObjectNode()); + } + + @Override + public ValidationResult validate(RestRequest request, JsonNode jsonNode) { + return ValidationResult.success(DefaultObjectMapper.objectMapper.createObjectNode()); + } + }; + + public final static String INVALID_KEYS_KEY = "invalid_keys"; + + /* public for testing */ + public final static String MISSING_MANDATORY_KEYS_KEY = "missing_mandatory_keys"; + + /* public for testing */ + public final static String MISSING_MANDATORY_OR_KEYS_KEY = "specify_one_of"; + + protected final static Logger LOGGER = LogManager.getLogger(RequestContentValidator.class); + + public static enum DataType { + STRING, + ARRAY, + OBJECT, + BOOLEAN; + } + + public interface ValidationContext { + + default boolean hasParams() { + return params() != null && params().length > 0; + } + + default Set mandatoryKeys() { + return Collections.emptySet(); + } + + default Set mandatoryOrKeys() { + return Collections.emptySet(); + } + + Object[] params(); + + Settings settings(); + + Map allowedKeys(); + + } + + protected ValidationError validationError; + + protected final ValidationContext validationContext; + + protected final Map wrongDataTypes = new HashMap<>(); + + /** Contain errorneous keys */ + private final Set missingMandatoryKeys = new HashSet<>(); + + private final Set invalidKeys = new HashSet<>(); + + private final Set missingMandatoryOrKeys = new HashSet<>(); + + protected RequestContentValidator(final ValidationContext validationContext) { + this.validationError = ValidationError.NONE; + this.validationContext = validationContext; + } + + public ValidationResult validate(final RestRequest request) throws IOException { + return parseRequestContent(request).map(this::validateContentSize).map(jsonContent -> validate(request, jsonContent)); + } + + public ValidationResult validate(final RestRequest request, final JsonNode jsonContent) throws IOException { + return validateContentSize(jsonContent).map(this::validateJsonKeys) + .map(this::validateDataType) + .map(this::nullValuesInArrayValidator) + .map(ignored -> validatePassword(request, jsonContent)); + } + + private ValidationResult parseRequestContent(final RestRequest request) { + try { + final JsonNode jsonContent = DefaultObjectMapper.readTree(request.content().utf8ToString()); + return ValidationResult.success(jsonContent); + } catch (final IOException ioe) { + LOGGER.error(ValidationError.BODY_NOT_PARSEABLE.message(), ioe); + this.validationError = ValidationError.BODY_NOT_PARSEABLE; + return ValidationResult.error(this); + } + } + + private ValidationResult validateContentSize(final JsonNode jsonContent) { + if (jsonContent.size() == 0) { + this.validationError = ValidationError.PAYLOAD_MANDATORY; + return ValidationResult.error(this); + } + return ValidationResult.success(jsonContent); + } + + private ValidationResult validateJsonKeys(final JsonNode jsonContent) { + final Set requestedKeys = new HashSet<>(); + jsonContent.fieldNames().forEachRemaining(requestedKeys::add); + // mandatory settings, one of ... + if (Collections.disjoint(requestedKeys, validationContext.mandatoryOrKeys())) { + missingMandatoryOrKeys.addAll(validationContext.mandatoryOrKeys()); + } + final Set mandatory = new HashSet<>(validationContext.mandatoryKeys()); + mandatory.removeAll(requestedKeys); + missingMandatoryKeys.addAll(mandatory); + + final Set allowed = new HashSet<>(validationContext.allowedKeys().keySet()); + requestedKeys.removeAll(allowed); + invalidKeys.addAll(requestedKeys); + if (!missingMandatoryKeys.isEmpty() || !invalidKeys.isEmpty() || !missingMandatoryOrKeys.isEmpty()) { + this.validationError = ValidationError.INVALID_CONFIGURATION; + return ValidationResult.error(this); + } + return ValidationResult.success(jsonContent); + } + + private ValidationResult validateDataType(final JsonNode jsonContent) { + try (final JsonParser parser = new JsonFactory().createParser(jsonContent.toString())) { + JsonToken token; + while ((token = parser.nextToken()) != null) { + if (token.equals(JsonToken.FIELD_NAME)) { + String currentName = parser.getCurrentName(); + final DataType dataType = validationContext.allowedKeys().get(currentName); + if (dataType != null) { + JsonToken valueToken = parser.nextToken(); + switch (dataType) { + case STRING: + if (valueToken != JsonToken.VALUE_STRING) { + wrongDataTypes.put(currentName, "String expected"); + } + break; + case ARRAY: + if (valueToken != JsonToken.START_ARRAY && valueToken != JsonToken.END_ARRAY) { + wrongDataTypes.put(currentName, "Array expected"); + } + break; + case OBJECT: + if (!valueToken.equals(JsonToken.START_OBJECT) && !valueToken.equals(JsonToken.END_OBJECT)) { + wrongDataTypes.put(currentName, "Object expected"); + } + break; + } + } + } + } + } catch (final IOException ioe) { + LOGGER.error("Couldn't create JSON for payload {}", jsonContent, ioe); + this.validationError = ValidationError.BODY_NOT_PARSEABLE; + return ValidationResult.error(this); + } + if (!wrongDataTypes.isEmpty()) { + this.validationError = ValidationError.WRONG_DATATYPE; + return ValidationResult.error(this); + } + return ValidationResult.success(jsonContent); + } + + private ValidationResult nullValuesInArrayValidator(final JsonNode jsonContent) { + for (final Map.Entry allowedKey : validationContext.allowedKeys().entrySet()) { + JsonNode value = jsonContent.get(allowedKey.getKey()); + if (value != null) { + if (hasNullArrayElement(value)) { + this.validationError = ValidationError.NULL_ARRAY_ELEMENT; + return ValidationResult.error(this); + } + } + } + return ValidationResult.success(jsonContent); + } + + private boolean hasNullArrayElement(final JsonNode node) { + for (final JsonNode element : node) { + if (element.isNull()) { + if (node.isArray()) { + return true; + } + } else if (element.isContainerNode()) { + if (hasNullArrayElement(element)) { + return true; + } + } + } + return false; + } + + private ValidationResult validatePassword(final RestRequest request, final JsonNode jsonContent) { + if (jsonContent.has("password")) { + final PasswordValidator passwordValidator = PasswordValidator.of(validationContext.settings()); + final String password = jsonContent.get("password").asText(); + if (Strings.isNullOrEmpty(password)) { + this.validationError = ValidationError.INVALID_PASSWORD; + return ValidationResult.error(this); + } + final String username = Utils.coalesce( + request.param("name"), + validationContext.hasParams() ? (String) validationContext.params()[0] : null + ); + if (Strings.isNullOrEmpty(username)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unable to validate username because no user is given"); + } + this.validationError = ValidationError.NO_USERNAME; + return ValidationResult.error(this); + } + this.validationError = passwordValidator.validate(username, password); + if (this.validationError != ValidationError.NONE) { + return ValidationResult.error(this); + } + } + return ValidationResult.success(jsonContent); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { + builder.startObject(); + switch (this.validationError) { + case INVALID_CONFIGURATION: + builder.field("status", "error"); + builder.field("reason", ValidationError.INVALID_CONFIGURATION.message()); + addErrorMessage(builder, INVALID_KEYS_KEY, invalidKeys); + addErrorMessage(builder, MISSING_MANDATORY_KEYS_KEY, missingMandatoryKeys); + addErrorMessage(builder, MISSING_MANDATORY_OR_KEYS_KEY, missingMandatoryOrKeys); + break; + case INVALID_PASSWORD: + builder.field("status", "error"); + builder.field( + "reason", + validationContext.settings() + .get(SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, "Password does not match minimum criteria") + ); + break; + case WEAK_PASSWORD: + case SIMILAR_PASSWORD: + builder.field("status", "error"); + builder.field( + "reason", + validationContext.settings().get(SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, validationError.message()) + ); + break; + case WRONG_DATATYPE: + builder.field("status", "error"); + builder.field("reason", ValidationError.WRONG_DATATYPE.message()); + for (Map.Entry entry : wrongDataTypes.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + break; + default: + builder.field("status", "error"); + builder.field("reason", validationError.message()); + break; + } + builder.endObject(); + return builder; + } + + private void addErrorMessage(final XContentBuilder builder, final String message, final Set keys) throws IOException { + if (!keys.isEmpty()) { + builder.startObject(message).field("keys", String.join(",", keys)).endObject(); + } + } + + public static RequestContentValidator of(final ValidationContext validationContext) { + return new RequestContentValidator(new ValidationContext() { + + private final Object[] params = validationContext.params(); + + private final Set mandatoryKeys = validationContext.mandatoryKeys(); + + private final Set mandatoryOrKeys = validationContext.mandatoryOrKeys(); + + private final Map allowedKeys = validationContext.allowedKeys(); + + @Override + public Settings settings() { + return validationContext.settings(); + } + + @Override + public Object[] params() { + return params; + } + + @Override + public Set mandatoryKeys() { + return mandatoryKeys; + } + + @Override + public Set mandatoryOrKeys() { + return mandatoryOrKeys; + } + + @Override + public Map allowedKeys() { + return allowedKeys; + } + }); + } + + public enum ValidationError { + NONE("ok"), + INVALID_CONFIGURATION("Invalid configuration"), + INVALID_PASSWORD("Invalid password"), + NO_USERNAME("No username is given"), + WEAK_PASSWORD("Weak password"), + SIMILAR_PASSWORD("Password is similar to user name"), + WRONG_DATATYPE("Wrong datatype"), + BODY_NOT_PARSEABLE("Could not parse content of request."), + PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."), + PAYLOAD_MANDATORY("Request body required for this action."), + SECURITY_NOT_INITIALIZED("Security index not initialized"), + NULL_ARRAY_ELEMENT("`null` is not allowed as json array element"); + + private final String message; + + private ValidationError(String message) { + this.message = message; + } + + public String message() { + return message; + } + + } +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java deleted file mode 100644 index 10c630c771..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class RolesMappingValidator extends AbstractConfigurationValidator { - - public RolesMappingValidator( - final RestRequest request, - boolean isSuperAdmin, - final BytesReference ref, - final Settings opensearchSettings, - Object... param - ) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("backend_roles", DataType.ARRAY); - allowedKeys.put("and_backend_roles", DataType.ARRAY); - allowedKeys.put("hosts", DataType.ARRAY); - allowedKeys.put("users", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - - mandatoryOrKeys.add("backend_roles"); - mandatoryOrKeys.add("and_backend_roles"); - mandatoryOrKeys.add("hosts"); - mandatoryOrKeys.add("users"); - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java deleted file mode 100644 index 019a21c73b..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import java.util.List; - -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.ReadContext; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; -import org.opensearch.security.configuration.MaskedField; -import org.opensearch.security.configuration.Salt; - -public class RolesValidator extends AbstractConfigurationValidator { - - private static final Salt SALT = new Salt(new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6 }); - - public RolesValidator( - final RestRequest request, - boolean isSuperAdmin, - final BytesReference ref, - final Settings opensearchSettings, - Object... param - ) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("cluster_permissions", DataType.ARRAY); - allowedKeys.put("tenant_permissions", DataType.ARRAY); - allowedKeys.put("index_permissions", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - } - - @Override - public boolean validate() { - - if (!super.validate()) { - return false; - } - - boolean valid = true; - - if (this.content != null && this.content.length() > 0) { - - final ReadContext ctx = JsonPath.parse(this.content.utf8ToString()); - final List maskedFields = ctx.read("$..masked_fields[*]"); - - if (maskedFields != null) { - - for (String mf : maskedFields) { - if (!validateMaskedFieldSyntax(mf)) { - valid = false; - } - } - } - } - - if (!valid) { - this.errorType = ErrorType.WRONG_DATATYPE; - } - - return valid; - } - - private boolean validateMaskedFieldSyntax(String mf) { - try { - new MaskedField(mf, SALT).isValid(); - } catch (Exception e) { - wrongDatatypes.put("Masked field not valid: " + mf, e.getMessage()); - return false; - } - return true; - } -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java deleted file mode 100644 index 30f3c91965..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class SecurityConfigValidator extends AbstractConfigurationValidator { - - public SecurityConfigValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("dynamic", DataType.OBJECT); - } - -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java deleted file mode 100644 index 07a0ec9cf3..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015-2017 floragunn GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class TenantValidator extends AbstractConfigurationValidator { - - public TenantValidator( - final RestRequest request, - boolean isSuperAdmin, - BytesReference ref, - final Settings opensearchSettings, - Object... param - ) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("description", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - } - -} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java b/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java new file mode 100644 index 0000000000..0247742a17 --- /dev/null +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/ValidationResult.java @@ -0,0 +1,70 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.validation; + +import com.fasterxml.jackson.databind.JsonNode; +import org.opensearch.common.CheckedConsumer; +import org.opensearch.common.CheckedFunction; +import org.opensearch.core.xcontent.ToXContent; + +import java.io.IOException; +import java.util.Objects; + +public class ValidationResult { + + private final JsonNode jsonContent; + + private final ToXContent errorMessage; + + private ValidationResult(final JsonNode jsonContent, final ToXContent errorMessage) { + this.jsonContent = jsonContent; + this.errorMessage = errorMessage; + } + + public static ValidationResult success(final JsonNode jsonContent) { + return new ValidationResult(jsonContent, null); + } + + public static ValidationResult error(final ToXContent errorMessage) { + return new ValidationResult(null, errorMessage); + } + + public ValidationResult map(final CheckedFunction validation) throws IOException { + if (jsonContent != null) { + return Objects.requireNonNull(validation).apply(jsonContent); + } else { + return this; + } + } + + public void error(final CheckedConsumer invalid) throws IOException { + if (errorMessage != null) { + Objects.requireNonNull(invalid).accept(errorMessage); + } + } + + public ValidationResult valid(final CheckedConsumer contentHandler) throws IOException { + if (jsonContent != null) { + Objects.requireNonNull(contentHandler).accept(jsonContent); + } + return this; + } + + public boolean isValid() { + return errorMessage == null; + } + + public ToXContent errorMessage() { + return errorMessage; + } + +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/WhitelistValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/WhitelistValidator.java deleted file mode 100644 index 91a283b4c5..0000000000 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/WhitelistValidator.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.validation; - -import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestRequest; - -public class WhitelistValidator extends AbstractConfigurationValidator { - - public WhitelistValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("enabled", DataType.BOOLEAN); - allowedKeys.put("requests", DataType.OBJECT); - } -} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java index c87698155f..46b730abac 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiTest.java @@ -24,7 +24,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -147,13 +147,13 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean userAdminCert) HttpResponse response = rh.executePutRequest(ENDPOINT + "/SOMEGROUP", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.PAYLOAD_MANDATORY.message(), settings.get("reason")); // put new configuration with invalid payload, must fail response = rh.executePutRequest(ENDPOINT + "/SOMEGROUP", FileHelper.loadFile("restapi/actiongroup_not_parseable.json"), header); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.BODY_NOT_PARSEABLE.message(), settings.get("reason")); response = rh.executePutRequest(ENDPOINT + "/CRUD_UT", FileHelper.loadFile("restapi/actiongroup_crud.json"), header); Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); @@ -553,7 +553,7 @@ public void checkNullElementsInArray() throws Exception { HttpResponse response = rh.executePutRequest(ENDPOINT + "/CRUD_UT", body, new Header[0]); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java index 79ce591723..ab0cf53237 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AllowlistApiTest.java @@ -26,7 +26,7 @@ import org.opensearch.security.auditlog.impl.AuditCategory; import org.opensearch.security.auditlog.impl.AuditMessage; import org.opensearch.security.auditlog.integration.TestAuditlogImpl; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.filter.SecurityRestFilter; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; @@ -142,7 +142,7 @@ public void testPayloadMandatory() throws Exception { response = rh.executePutRequest(ENDPOINT, "", new Header[0]); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason").asText()); + Assert.assertEquals(RequestContentValidator.ValidationError.PAYLOAD_MANDATORY.message(), settings.get("reason").asText()); } /** diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java index a549ad4dd9..8a429022a4 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/NodesDnApiTest.java @@ -29,7 +29,7 @@ import org.opensearch.security.auditlog.impl.AuditCategory; import org.opensearch.security.auditlog.impl.AuditMessage; import org.opensearch.security.auditlog.integration.TestAuditlogImpl; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -124,7 +124,7 @@ private void checkNullElementsInArray(final Header headers) throws Exception { HttpResponse response = rh.executePutRequest(ENDPOINT + "/nodesdn/cluster1", body, headers); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } @Test diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java index bf703fea35..95c9c432ab 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesApiTest.java @@ -11,8 +11,6 @@ package org.opensearch.security.dlic.rest.api; -import java.util.List; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -21,15 +19,16 @@ import org.apache.hc.core5.http.HttpStatus; import org.junit.Assert; import org.junit.Test; - import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.support.SecurityJsonNode; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; +import java.util.List; + import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; @@ -247,7 +246,7 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason").asText()); + Assert.assertEquals(RequestContentValidator.ValidationError.PAYLOAD_MANDATORY.message(), settings.get("reason").asText()); // put new configuration with invalid payload, must fail response = rh.executePutRequest( @@ -257,7 +256,7 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) ); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason").asText()); + Assert.assertEquals(RequestContentValidator.ValidationError.BODY_NOT_PARSEABLE.message(), settings.get("reason").asText()); // put new configuration with invalid keys, must fail response = rh.executePutRequest( @@ -267,13 +266,9 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) ); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason").asText()); - Assert.assertTrue( - settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY).get("keys").asText().contains("indexx_permissions") - ); - Assert.assertTrue( - settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY).get("keys").asText().contains("kluster_permissions") - ); + Assert.assertEquals(RequestContentValidator.ValidationError.INVALID_CONFIGURATION.message(), settings.get("reason").asText()); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY).get("keys").asText().contains("indexx_permissions")); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY).get("keys").asText().contains("kluster_permissions")); // put new configuration with wrong datatypes, must fail response = rh.executePutRequest( @@ -283,7 +278,7 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) ); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason").asText()); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason").asText()); Assert.assertTrue(settings.get("cluster_permissions").asText().equals("Array expected")); // put read only role, must be forbidden @@ -495,7 +490,7 @@ void verifyPutForSuperAdmin(final Header[] header, final boolean sendAdminCert) Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = DefaultObjectMapper.readTree(response.getBody()); Assert.assertEquals(settings.get("status").asText(), "error"); - Assert.assertEquals(settings.get("reason").asText(), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); + Assert.assertEquals(settings.get("reason").asText(), RequestContentValidator.ValidationError.INVALID_CONFIGURATION.message()); } void verifyPatchForSuperAdmin(final Header[] header, final boolean sendAdminCert) throws Exception { @@ -887,37 +882,37 @@ public void checkNullElementsInArray() throws Exception { HttpResponse response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_index_permissions.json"); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_tenant_permissions.json"); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_index_patterns.json"); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_masked_fields.json"); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/roles_null_array_element_allowed_actions.json"); response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_starfleet", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java index 42fb111281..4cfe057388 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java @@ -24,7 +24,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; @@ -239,7 +239,7 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.PAYLOAD_MANDATORY.message(), settings.get("reason")); // put new configuration with invalid payload, must fail response = rh.executePutRequest( @@ -249,7 +249,7 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.BODY_NOT_PARSEABLE.message(), settings.get("reason")); // put new configuration with invalid keys, must fail response = rh.executePutRequest( @@ -259,10 +259,10 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage(), settings.get("reason")); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("theusers")); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts")); + Assert.assertEquals(RequestContentValidator.ValidationError.INVALID_CONFIGURATION.message(), settings.get("reason")); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("theusers")); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts")); // wrong datatypes response = rh.executePutRequest( @@ -272,7 +272,7 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); Assert.assertTrue(settings.get("hosts") == null); Assert.assertTrue(settings.get("users") == null); @@ -284,7 +284,7 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("hosts").equals("Array expected")); Assert.assertTrue(settings.get("backend_roles") == null); Assert.assertTrue(settings.get("users") == null); @@ -296,7 +296,7 @@ void verifyPutForSuperAdmin(final Header[] header) throws Exception { ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("hosts").equals("Array expected")); Assert.assertTrue(settings.get("users").equals("Array expected")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); @@ -652,18 +652,18 @@ public void checkNullElementsInArray() throws Exception { ); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_backend_roles.json"); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_hosts.json"); response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", body, new Header[0]); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 895f65bc81..707dbe614e 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -23,8 +23,8 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentType; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.dlic.rest.validation.PasswordValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; @@ -192,14 +192,14 @@ private void verifyPut(final Header... header) throws Exception { response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\" asd other: \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage()); + Assert.assertEquals(settings.get("reason"), RequestContentValidator.ValidationError.BODY_NOT_PARSEABLE.message()); // Missing quotes in JSON - parseable in 6.x, but wrong config keys response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{some: \"thing\", other: \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); // JK: this should be "Could not parse content of request." because JSON is truly invalid - // Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); + // Assert.assertEquals(settings.get("reason"), ValidationError.INVALID_CONFIGURATION.message()); // Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); // Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); @@ -238,9 +238,9 @@ private void verifyPut(final Header... header) throws Exception { response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{\"some\": \"thing\", \"other\": \"thing\"}", header); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage()); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some")); - Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other")); + Assert.assertEquals(settings.get("reason"), RequestContentValidator.ValidationError.INVALID_CONFIGURATION.message()); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("some")); + Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("other")); } @@ -518,7 +518,7 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E ); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; @@ -530,7 +530,7 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; @@ -542,7 +542,7 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("password").equals("String expected")); Assert.assertTrue(settings.get("backend_roles") == null); rh.sendAdminCertificate = false; @@ -555,7 +555,7 @@ private void verifyRoles(final boolean sendAdminCert, Header... header) throws E ); settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); rh.sendAdminCertificate = false; @@ -792,18 +792,18 @@ public void testScoreBasedPasswordRules() throws Exception { "admin", "password89", HttpStatus.SC_BAD_REQUEST, - AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() + RequestContentValidator.ValidationError.WEAK_PASSWORD.message() ); addUserWithPassword( "admin", "A123456789", HttpStatus.SC_BAD_REQUEST, - AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD.getMessage() + RequestContentValidator.ValidationError.WEAK_PASSWORD.message() ); addUserWithPassword("admin", "pas", HttpStatus.SC_BAD_REQUEST, "Password does not match minimum criteria"); - verifySimilarity(AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD.getMessage()); + verifySimilarity(RequestContentValidator.ValidationError.SIMILAR_PASSWORD.message()); addUserWithPassword("some_user_name", "ASSDsadwe324wadaasdadqwe", HttpStatus.SC_CREATED); } @@ -1001,7 +1001,7 @@ public void checkNullElementsInArray() throws Exception { HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/picard", body, new Header[0]); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.NULL_ARRAY_ELEMENT.getMessage(), settings.get("reason")); + Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java index 371341147e..8a1d8ce83f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/WhitelistApiTest.java @@ -26,7 +26,7 @@ import org.opensearch.security.auditlog.impl.AuditCategory; import org.opensearch.security.auditlog.impl.AuditMessage; import org.opensearch.security.auditlog.integration.TestAuditlogImpl; -import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.filter.SecurityRestFilter; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; @@ -149,7 +149,7 @@ public void testPayloadMandatory() throws Exception { response = rh.executePutRequest(ENDPOINT + "/whitelist", "", new Header[0]); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); JsonNode settings = DefaultObjectMapper.readTree(response.getBody()); - Assert.assertEquals(AbstractConfigurationValidator.ErrorType.PAYLOAD_MANDATORY.getMessage(), settings.get("reason").asText()); + Assert.assertEquals(RequestContentValidator.ValidationError.PAYLOAD_MANDATORY.message(), settings.get("reason").asText()); } /** diff --git a/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java index 7ea6f23898..6f2648d5dd 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/validation/PasswordValidatorTest.java @@ -66,7 +66,7 @@ public class PasswordValidatorTest { public void verifyWeakPasswords( final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult + final RequestContentValidator.ValidationError expectedValidationResult ) { for (final String password : WEAK_PASSWORDS) assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); @@ -75,7 +75,7 @@ public void verifyWeakPasswords( public void verifyFairPasswords( final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult + final RequestContentValidator.ValidationError expectedValidationResult ) { for (final String password : FAIR_PASSWORDS) assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); @@ -84,7 +84,7 @@ public void verifyFairPasswords( public void verifyGoodPasswords( final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult + final RequestContentValidator.ValidationError expectedValidationResult ) { for (final String password : GOOD_PASSWORDS) assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); @@ -93,7 +93,7 @@ public void verifyGoodPasswords( public void verifyStrongPasswords( final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult + final RequestContentValidator.ValidationError expectedValidationResult ) { for (final String password : STRONG_PASSWORDS) assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); @@ -102,7 +102,7 @@ public void verifyStrongPasswords( public void verifyVeryStrongPasswords( final PasswordValidator passwordValidator, - final AbstractConfigurationValidator.ErrorType expectedValidationResult + final RequestContentValidator.ValidationError expectedValidationResult ) { for (final String password : VERY_STRONG_PASSWORDS) assertEquals(password, expectedValidationResult, passwordValidator.validate("some_user_name", password)); @@ -113,7 +113,7 @@ public void verifySimilarPasswords(final PasswordValidator passwordValidator) { for (final String password : SIMILAR_PASSWORDS) assertEquals( password, - AbstractConfigurationValidator.ErrorType.SIMILAR_PASSWORD, + RequestContentValidator.ValidationError.SIMILAR_PASSWORD, passwordValidator.validate("some_user_name", password) ); @@ -126,22 +126,22 @@ public void testRegExpBasedValidation() { .put(SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}") .build() ); - verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD); - verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD); + verifyWeakPasswords(passwordValidator, RequestContentValidator.ValidationError.INVALID_PASSWORD); + verifyFairPasswords(passwordValidator, RequestContentValidator.ValidationError.INVALID_PASSWORD); for (final String password : GOOD_PASSWORDS.subList(0, GOOD_PASSWORDS.size() - 2)) assertEquals( password, - AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, + RequestContentValidator.ValidationError.INVALID_PASSWORD, passwordValidator.validate("some_user_name", password) ); for (final String password : GOOD_PASSWORDS.subList(GOOD_PASSWORDS.size() - 2, GOOD_PASSWORDS.size())) assertEquals( password, - AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD, + RequestContentValidator.ValidationError.WEAK_PASSWORD, passwordValidator.validate("some_user_name", password) ); - verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); - verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyStrongPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); + verifyVeryStrongPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); verifySimilarPasswords(passwordValidator); } @@ -151,7 +151,7 @@ public void testMinLength() { Settings.builder().put(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 15).build() ); for (final String password : STRONG_PASSWORDS) { - assertEquals(AbstractConfigurationValidator.ErrorType.INVALID_PASSWORD, passwordValidator.validate(password, "some_user_name")); + assertEquals(RequestContentValidator.ValidationError.INVALID_PASSWORD, passwordValidator.validate(password, "some_user_name")); } } @@ -159,11 +159,11 @@ public void testMinLength() { @Test public void testScoreBasedValidation() { PasswordValidator passwordValidator = PasswordValidator.of(Settings.EMPTY); - verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); - verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); - verifyGoodPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); - verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); - verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyWeakPasswords(passwordValidator, RequestContentValidator.ValidationError.WEAK_PASSWORD); + verifyFairPasswords(passwordValidator, RequestContentValidator.ValidationError.WEAK_PASSWORD); + verifyGoodPasswords(passwordValidator, RequestContentValidator.ValidationError.WEAK_PASSWORD); + verifyStrongPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); + verifyVeryStrongPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); verifySimilarPasswords(passwordValidator); passwordValidator = PasswordValidator.of( @@ -172,11 +172,11 @@ public void testScoreBasedValidation() { .build() ); - verifyWeakPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.WEAK_PASSWORD); - verifyFairPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); - verifyGoodPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); - verifyStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); - verifyVeryStrongPasswords(passwordValidator, AbstractConfigurationValidator.ErrorType.NONE); + verifyWeakPasswords(passwordValidator, RequestContentValidator.ValidationError.WEAK_PASSWORD); + verifyFairPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); + verifyGoodPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); + verifyStrongPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); + verifyVeryStrongPasswords(passwordValidator, RequestContentValidator.ValidationError.NONE); verifySimilarPasswords(passwordValidator); } diff --git a/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java new file mode 100644 index 0000000000..ee98d2a10b --- /dev/null +++ b/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java @@ -0,0 +1,320 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.dlic.rest.validation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opensearch.common.Strings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.bytes.BytesArray; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.http.HttpChannel; +import org.opensearch.http.HttpRequest; +import org.opensearch.rest.RestRequest; +import org.opensearch.security.DefaultObjectMapper; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class RequestContentValidatorTest { + + @Mock + private HttpRequest httpRequest; + + @Mock + private NamedXContentRegistry xContentRegistry; + + @Mock + private HttpChannel httpChannel; + + private RestRequest request; + + @Before + public void setUpRequest() { + when(httpRequest.uri()).thenReturn(""); + when(httpRequest.content()).thenReturn(new BytesArray(new byte[1])); + when(httpRequest.getHeaders()).thenReturn( + Collections.singletonMap("Content-Type", Collections.singletonList("application/json")) + ); + request = RestRequest.request(xContentRegistry, httpRequest, httpChannel); + } + + @Test + public void testParseRequestContent() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Map allowedKeys() { + return Collections.emptyMap(); + } + }); + when(httpRequest.content()).thenReturn(new BytesArray("{`a`: `b`}")); + final ValidationResult validationResult = validator.validate(request); + assertFalse(validationResult.isValid()); + assertErrorMessage(validationResult.errorMessage(), RequestContentValidator.ValidationError.BODY_NOT_PARSEABLE); + } + + @Test + public void testValidateContentSize() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Map allowedKeys() { + return Collections.emptyMap(); + } + }); + when(httpRequest.content()).thenReturn(new BytesArray("")); + ValidationResult validationResult = validator.validate(request); + assertFalse(validationResult.isValid()); + assertErrorMessage(validationResult.errorMessage(), RequestContentValidator.ValidationError.PAYLOAD_MANDATORY); + + when(httpRequest.content()).thenReturn(new BytesArray("{}")); + validationResult = validator.validate(request); + assertFalse(validationResult.isValid()); + assertErrorMessage(validationResult.errorMessage(), RequestContentValidator.ValidationError.PAYLOAD_MANDATORY); + } + + @Test + public void testValidateDataTypes() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of( + "a", + RequestContentValidator.DataType.STRING, + "b", + RequestContentValidator.DataType.OBJECT, + "c", + RequestContentValidator.DataType.ARRAY + ); + } + }); + + final JsonNode payload = DefaultObjectMapper.objectMapper.createObjectNode().put("a", 1).put("b", "[]").put("c", "{}"); + when(httpRequest.content()).thenReturn(new BytesArray(payload.toString())); + final ValidationResult validationResult = validator.validate(request); + + final JsonNode errorMessage = xContentToJsonNode(validationResult.errorMessage()); + assertFalse(validationResult.isValid()); + assertErrorMessage(errorMessage, RequestContentValidator.ValidationError.WRONG_DATATYPE); + + assertEquals("String expected", errorMessage.get("a").asText()); + assertEquals("Object expected", errorMessage.get("b").asText()); + assertEquals("Array expected", errorMessage.get("c").asText()); + } + + @Test + public void testValidateJsonKeys() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Set mandatoryKeys() { + return Set.of("a"); + } + + @Override + public Map allowedKeys() { + return Map.of("a", RequestContentValidator.DataType.STRING, "b", RequestContentValidator.DataType.STRING); + } + }); + + final JsonNode payload = DefaultObjectMapper.objectMapper.createObjectNode().put("c", "aaa").put("d", "aaa"); + when(httpRequest.content()).thenReturn(new BytesArray(payload.toString())); + final ValidationResult validationResult = validator.validate(request); + final JsonNode errorMessage = xContentToJsonNode(validationResult.errorMessage()); + assertErrorMessage(errorMessage, RequestContentValidator.ValidationError.INVALID_CONFIGURATION); + + assertEquals("{\"keys\":\"c,d\"}", errorMessage.get("invalid_keys").toString()); + assertEquals("{\"keys\":\"a\"}", errorMessage.get("missing_mandatory_keys").toString()); + } + + @Test + public void testNullValuesInArray() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Set mandatoryKeys() { + return ImmutableSet.of("a"); + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("a", RequestContentValidator.DataType.ARRAY); + } + }); + final ObjectNode payload = DefaultObjectMapper.objectMapper.createObjectNode().putObject("a"); + payload.putArray("a").add(NullNode.getInstance()).add("b").add("c"); + when(request.content()).thenReturn(new BytesArray(payload.toString())); + final ValidationResult validationResult = validator.validate(request); + assertErrorMessage(validationResult.errorMessage(), RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT); + } + + @Test + public void testValidatePassword() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Set mandatoryKeys() { + return ImmutableSet.of("password"); + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of("password", RequestContentValidator.DataType.STRING); + } + }); + ObjectNode payload = DefaultObjectMapper.objectMapper.createObjectNode().put("password", "a"); + when(httpRequest.content()).thenReturn(new BytesArray(payload.toString())); + ValidationResult validationResult = validator.validate(request); + assertErrorMessage(validationResult.errorMessage(), RequestContentValidator.ValidationError.NO_USERNAME); + + when(httpRequest.uri()).thenReturn("/aaaa?name=a"); + when(request.content()).thenReturn(new BytesArray(payload.toString())); + validationResult = validator.validate(RestRequest.request(xContentRegistry, httpRequest, httpChannel)); + assertErrorMessage(validationResult.errorMessage(), RequestContentValidator.ValidationError.WEAK_PASSWORD); + } + + @Test + public void testValidationSuccess() throws Exception { + final RequestContentValidator validator = RequestContentValidator.of(new RequestContentValidator.ValidationContext() { + @Override + public Object[] params() { + return new Object[0]; + } + + @Override + public Settings settings() { + return Settings.EMPTY; + } + + @Override + public Map allowedKeys() { + return ImmutableMap.of( + "a", + RequestContentValidator.DataType.ARRAY, + "b", + RequestContentValidator.DataType.BOOLEAN, + "c", + RequestContentValidator.DataType.OBJECT, + "d", + RequestContentValidator.DataType.STRING, + "e", + RequestContentValidator.DataType.BOOLEAN + ); + } + }); + + ObjectNode payload = DefaultObjectMapper.objectMapper.createObjectNode().putObject("a"); + payload.putArray("a").add("arrray"); + payload.put("b", true).put("d", "some_string").put("e", "true"); + payload.putObject("c"); + + when(httpRequest.content()).thenReturn(new BytesArray(payload.toString())); + final ValidationResult validationResult = validator.validate(request); + assertTrue(validationResult.isValid()); + assertNull(validationResult.errorMessage()); + } + + private JsonNode xContentToJsonNode(final ToXContent toXContent) throws IOException { + try (final var xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON)) { + toXContent.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); + return DefaultObjectMapper.readTree(Strings.toString(xContentBuilder)); + } + } + + private void assertErrorMessage(final ToXContent toXContent, final RequestContentValidator.ValidationError expectedValidationError) + throws IOException { + final var jsonNode = xContentToJsonNode(toXContent); + assertErrorMessage(jsonNode, expectedValidationError); + } + + private void assertErrorMessage(final JsonNode jsonNode, final RequestContentValidator.ValidationError expectedValidationError) { + assertEquals("error", jsonNode.get("status").asText()); + assertEquals(expectedValidationError.message(), jsonNode.get("reason").asText()); + } + +} From d3488e8c3a8c682277105ad84ccbc0a2df813194 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:15:59 -0400 Subject: [PATCH 02/12] dependabot: bump kafka_version from 3.5.0 to 3.5.1 (#3041) Bumps `kafka_version` from 3.5.0 to 3.5.1. Updates `org.apache.kafka:kafka-clients` from 3.5.0 to 3.5.1 Updates `org.apache.kafka:kafka_2.13` from 3.5.0 to 3.5.1 Updates `org.apache.kafka:kafka-group-coordinator` from 3.5.0 to 3.5.1 Updates `org.apache.kafka:kafka-metadata` from 3.5.0 to 3.5.1 Updates `org.apache.kafka:kafka-storage` from 3.5.0 to 3.5.1 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---

Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 87fe0d7934..813abd6f4f 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ buildscript { opensearch_build = version_tokens[0] + '.0' common_utils_version = System.getProperty("common_utils.version", '3.0.0.0-SNAPSHOT') - kafka_version = '3.5.0' + kafka_version = '3.5.1' apache_cxf_version = '4.0.2' open_saml_version = '4.3.0' one_login_java_saml = '2.9.0' From 08d17343c0821f7b7528a9559fc3384af5aa2c5b Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 31 Jul 2023 09:20:23 -0400 Subject: [PATCH 03/12] Fix build after Lucene upgrade and breaking XContentFactory changes (#3069) There are multiple PRs in core affecting the security plugin that the security plugin needs to adapt to. - https://github.com/opensearch-project/OpenSearch/pull/7792 - https://github.com/opensearch-project/OpenSearch/pull/8826 - https://github.com/opensearch-project/OpenSearch/pull/8668 I am opening a Draft PR that includes a fix for the Lucene-related test failures which was caused by https://github.com/opensearch-project/OpenSearch/pull/7792 Resolves: https://github.com/opensearch-project/security/issues/3064 Signed-off-by: Craig Perkins --- .../configuration/DlsFlsFilterLeafReader.java | 25 +++++++++++++++++++ .../security/support/ConfigHelper.java | 2 +- .../security/tools/SecurityAdmin.java | 2 +- .../RequestContentValidatorTest.java | 3 +-- .../security/test/helper/file/FileHelper.java | 4 +-- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index 508c3dd9b4..0966a3f3ac 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -46,6 +46,7 @@ import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.index.StoredFieldVisitor; +import org.apache.lucene.index.StoredFields; import org.apache.lucene.index.TermState; import org.apache.lucene.index.Terms; import org.apache.lucene.index.TermsEnum; @@ -473,6 +474,24 @@ public void close() throws IOException { } } + private class DlsFlsStoredFields extends StoredFields { + private final StoredFields in; + + public DlsFlsStoredFields(StoredFields storedFields) { + this.in = storedFields; + } + + @Override + public void document(final int docID, StoredFieldVisitor visitor) throws IOException { + visitor = getDlsFlsVisitor(visitor); + try { + in.document(docID, visitor); + } finally { + finishVisitor(visitor); + } + } + } + @Override protected StoredFieldsReader doGetSequentialStoredFieldsReader(final StoredFieldsReader reader) { return new DlsFlsStoredFieldsReader(reader); @@ -1284,6 +1303,12 @@ public TermState termState() throws IOException { } + @Override + public StoredFields storedFields() throws IOException { + ensureOpen(); + return new DlsFlsStoredFields(in.storedFields()); + } + private String getRuntimeActionName() { return (String) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ACTION_NAME); } diff --git a/src/main/java/org/opensearch/security/support/ConfigHelper.java b/src/main/java/org/opensearch/security/support/ConfigHelper.java index 92bf069028..434a8a02fa 100644 --- a/src/main/java/org/opensearch/security/support/ConfigHelper.java +++ b/src/main/java/org/opensearch/security/support/ConfigHelper.java @@ -141,7 +141,7 @@ public static BytesReference readXContent(final Reader reader, final MediaType m BytesReference retVal; XContentParser parser = null; try { - parser = XContentFactory.xContent(mediaType).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, reader); + parser = mediaType.xContent().createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, reader); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); builder.copyCurrentStructure(parser); diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index d2dc85f9d2..7d8a8a7b0b 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -1202,7 +1202,7 @@ private static BytesReference readXContent(final String content, final MediaType BytesReference retVal; XContentParser parser = null; try { - parser = XContentFactory.xContent(mediaType).createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, content); + parser = mediaType.xContent().createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, content); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); builder.copyCurrentStructure(parser); diff --git a/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java b/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java index ee98d2a10b..55b8664188 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/validation/RequestContentValidatorTest.java @@ -24,7 +24,6 @@ import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.common.bytes.BytesArray; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.ToXContent; @@ -300,7 +299,7 @@ public Map allowedKeys() { } private JsonNode xContentToJsonNode(final ToXContent toXContent) throws IOException { - try (final var xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON)) { + try (final var xContentBuilder = XContentFactory.jsonBuilder()) { toXContent.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); return DefaultObjectMapper.readTree(Strings.toString(xContentBuilder)); } diff --git a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java index 90adca6a91..df1495ed10 100644 --- a/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java +++ b/src/test/java/org/opensearch/security/test/helper/file/FileHelper.java @@ -110,7 +110,7 @@ public static BytesReference readYamlContent(final String file) { XContentParser parser = null; try { - parser = XContentFactory.xContent(XContentType.YAML) + parser = XContentType.YAML.xContent() .createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(loadFile(file))); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); @@ -133,7 +133,7 @@ public static BytesReference readYamlContentFromString(final String yaml) { XContentParser parser = null; try { - parser = XContentFactory.xContent(XContentType.YAML) + parser = XContentType.YAML.xContent() .createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, new StringReader(yaml)); parser.nextToken(); final XContentBuilder builder = XContentFactory.jsonBuilder(); From cca77bec5350d59d4c8570c4dee9834ff9b3c33c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:32:50 -0400 Subject: [PATCH 04/12] Remove static local-node reference (#3066) ### Description Remove static reference/initialization of localNode variable inside security plugin, to fix `No user found..` errors caused due to mismatching localNode values in test. Signed-off-by: Peter Nied Co-authored-by: Peter Nied --- .../security/OpenSearchSecurityPlugin.java | 14 +++----------- .../security/transport/SecurityInterceptor.java | 6 +++--- .../transport/SecurityInterceptorTests.java | 7 ++----- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 1dbc787b74..e46a04f81f 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -212,7 +212,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile ConfigurationRepository cr; private volatile AdminDNs adminDns; private volatile ClusterService cs; - private static volatile DiscoveryNode localNode; + private volatile AtomicReference localNode = new AtomicReference<>(); private volatile AuditLog auditLog; private volatile BackendRegistry backendRegistry; private volatile SslExceptionHandler sslExceptionHandler; @@ -776,7 +776,7 @@ public void sendRequest( TransportRequestOptions options, TransportResponseHandler handler ) { - si.sendRequestDecorate(sender, connection, action, request, options, handler); + si.sendRequestDecorate(sender, connection, action, request, options, handler, localNode.get()); } }; } @@ -1806,7 +1806,7 @@ public void onNodeStarted(DiscoveryNode localNode) { if (!SSLConfig.isSslOnlyMode() && !client && !disabled) { cr.initOnNodeStart(); } - this.localNode = localNode; + this.localNode.set(localNode); final Set securityModules = ReflectionHelper.getModulesLoaded(); log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); } @@ -1886,14 +1886,6 @@ private static String handleKeyword(final String field) { return field; } - public static DiscoveryNode getLocalNode() { - return localNode; - } - - public static void setLocalNode(DiscoveryNode node) { - localNode = node; - } - public static class GuiceHolder implements LifecycleComponent { private static RepositoriesService repositoriesService; diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index 015704e087..5bb4b1d1e3 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -130,7 +130,8 @@ public void sendRequestDecorate( String action, TransportRequest request, TransportRequestOptions options, - TransportResponseHandler handler + TransportResponseHandler handler, + DiscoveryNode localNode ) { final Map origHeaders0 = getThreadContext().getHeaders(); final User user0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); @@ -146,8 +147,7 @@ public void sendRequestDecorate( final String origCCSTransientMf = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_CCS); final boolean isDebugEnabled = log.isDebugEnabled(); - final DiscoveryNode localNode = OpenSearchSecurityPlugin.getLocalNode(); - boolean isSameNodeRequest = localNode != null && localNode.equals(connection.getNode()); + final boolean isSameNodeRequest = localNode != null && localNode.equals(connection.getNode()); try (ThreadContext.StoredContext stashedContext = getThreadContext().stashContext()) { final TransportResponseHandler restoringHandler = new RestoringTransportResponseHandler(handler, stashedContext); diff --git a/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java b/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java index 7291050d6e..6c16e0cdbd 100644 --- a/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java +++ b/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java @@ -147,11 +147,8 @@ public void testSendRequestDecorate() { DiscoveryNode otherNode = new DiscoveryNode("local-node", OpenSearchTestCase.buildNewFakeTransportAddress(), Version.CURRENT); Connection connection2 = transportService.getConnection(otherNode); - // setting localNode value explicitly - OpenSearchSecurityPlugin.setLocalNode(localNode); - // isSameNodeRequest = true - securityInterceptor.sendRequestDecorate(sender, connection1, action, request, options, handler); + securityInterceptor.sendRequestDecorate(sender, connection1, action, request, options, handler, localNode); // from thread context inside sendRequestDecorate doAnswer(i -> { User transientUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); @@ -165,7 +162,7 @@ public void testSendRequestDecorate() { assertEquals(threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER), null); // isSameNodeRequest = false - securityInterceptor.sendRequestDecorate(sender, connection2, action, request, options, handler); + securityInterceptor.sendRequestDecorate(sender, connection2, action, request, options, handler, otherNode); // checking thread context inside sendRequestDecorate doAnswer(i -> { String serializedUserHeader = threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); From c0e50da8a13161c3996aab6d20904ebd40b7b837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:02:04 -0400 Subject: [PATCH 05/12] dependabot: bump org.cryptacular:cryptacular from 1.2.4 to 1.2.5 (#3071) Bumps [org.cryptacular:cryptacular](https://github.com/vt-middleware/cryptacular) from 1.2.4 to 1.2.5.
Release notes

Sourced from org.cryptacular:cryptacular's releases.

v1.2.5 release

See https://www.cryptacular.org/download.html for change log and binaries.

Commits
  • 39eead7 Update version for 1.2.5 release.
  • 75b6c39 Remove travis config.
  • ae821d2 Update plugin and library dependencies.
  • f7dbd7a Improve release script.
  • 5b28d50 Update plugin versions.
  • 3419f66 Use variable for computing buffer length. (#60)
  • bef8a9f Add publish snapshot script.
  • 5285b5e Update plugin and library dependencies.
  • 04e243c Formatting fix.
  • 99d4d90 Update copyright year.
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.cryptacular:cryptacular&package-manager=gradle&previous-version=1.2.4&new-version=1.2.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 813abd6f4f..5490e25193 100644 --- a/build.gradle +++ b/build.gradle @@ -524,7 +524,7 @@ dependencies { runtimeOnly 'com.sun.activation:jakarta.activation:1.2.2' runtimeOnly 'com.eclipsesource.minimal-json:minimal-json:0.9.5' runtimeOnly 'commons-codec:commons-codec:1.16.0' - runtimeOnly 'org.cryptacular:cryptacular:1.2.4' + runtimeOnly 'org.cryptacular:cryptacular:1.2.5' runtimeOnly 'com.google.errorprone:error_prone_annotations:2.20.0' runtimeOnly 'com.sun.istack:istack-commons-runtime:4.2.0' runtimeOnly 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0' From 2f69a102134e2f946938cccfa8042d4ab9ce7778 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 31 Jul 2023 16:02:22 -0400 Subject: [PATCH 06/12] bump com.github.wnameless.json:json-base from 2.4.0 to 2.4.1 (#3062) Manually re-creating dependabot's [PR](https://github.com/opensearch-project/security/pull/3042) off of the latest changes from main which has build fixes. Is there a way we can open PRs with main at the push of a button? Signed-off-by: Craig Perkins --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5490e25193..904559cb48 100644 --- a/build.gradle +++ b/build.gradle @@ -496,7 +496,7 @@ dependencies { implementation "io.jsonwebtoken:jjwt-impl:${jjwt_version}" implementation "io.jsonwebtoken:jjwt-jackson:${jjwt_version}" // JSON flattener - implementation ("com.github.wnameless.json:json-base:2.4.0") { + implementation ("com.github.wnameless.json:json-base:2.4.1") { exclude group: "org.glassfish", module: "jakarta.json" exclude group: "com.google.code.gson", module: "gson" exclude group: "org.json", module: "json" From 5f62e8a9153bdfe1b9f89ead3a59f3022f0ccf63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:11:07 -0400 Subject: [PATCH 07/12] dependabot: bump commons-io:commons-io from 2.11.0 to 2.13.0 (#3074) Bumps commons-io:commons-io from 2.11.0 to 2.13.0. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=commons-io:commons-io&package-manager=gradle&previous-version=2.11.0&new-version=2.13.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 904559cb48..3d51b9600e 100644 --- a/build.gradle +++ b/build.gradle @@ -630,7 +630,7 @@ dependencies { integrationTestImplementation 'junit:junit:4.13.2' integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}" integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" - integrationTestImplementation 'commons-io:commons-io:2.11.0' + integrationTestImplementation 'commons-io:commons-io:2.13.0' integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}" integrationTestImplementation 'org.hamcrest:hamcrest:2.2' From dca76cb80cd7f665a0a292504fd8c70034005185 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:11:24 -0400 Subject: [PATCH 08/12] dependabot: bump io.dropwizard.metrics:metrics-core from 3.1.2 to 4.2.19 (#3073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.dropwizard.metrics:metrics-core](https://github.com/dropwizard/metrics) from 3.1.2 to 4.2.19.
Release notes

Sourced from io.dropwizard.metrics:metrics-core's releases.

v4.2.19

What's Changed

... (truncated)

Commits
  • 5b9ab08 [maven-release-plugin] prepare release v4.2.19
  • 5abe0ed Add workflow to trigger a new release
  • 6cc440a Fix NPE in InstrumentedHandler#doStop (Jetty 9, 10, 11) (#3379)
  • 8cd308b Generate metadata for reflection on method parameters (#3377)
  • 07f47a3 Update dependency org.apache.maven.plugins:maven-project-info-reports-plugin ...
  • b9464a2 Fix build with maven-source-plugin
  • 96a8d8e Update dependency org.apache.maven.plugins:maven-dependency-plugin to v3.6.0 ...
  • 2241714 Update dependency org.apache.maven.plugins:maven-checkstyle-plugin to v3.3.0 ...
  • d7b813f Update dependency org.apache.maven.plugins:maven-source-plugin to v3.3.0 (#3369)
  • 0e8c3ce Update dependency org.apache.felix:maven-bundle-plugin to v5.1.9
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.dropwizard.metrics:metrics-core&package-manager=gradle&previous-version=3.1.2&new-version=4.2.19)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3d51b9600e..28bc02340c 100644 --- a/build.gradle +++ b/build.gradle @@ -559,7 +559,7 @@ dependencies { runtimeOnly 'com.google.j2objc:j2objc-annotations:2.8' runtimeOnly 'com.google.code.findbugs:jsr305:3.0.2' runtimeOnly 'org.lz4:lz4-java:1.8.0' - runtimeOnly 'io.dropwizard.metrics:metrics-core:3.1.2' + runtimeOnly 'io.dropwizard.metrics:metrics-core:4.2.19' runtimeOnly 'org.slf4j:slf4j-api:1.7.30' runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" runtimeOnly 'org.xerial.snappy:snappy-java:1.1.10.1' From ac57b48fa421ce0657ae511cec94da081f46b93e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:11:39 -0400 Subject: [PATCH 09/12] dependabot: bump org.gradle.test-retry from 1.5.2 to 1.5.4 (#3072) Bumps org.gradle.test-retry from 1.5.2 to 1.5.4. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.gradle.test-retry&package-manager=gradle&previous-version=1.5.2&new-version=1.5.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 28bc02340c..9391b26017 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ plugins { id 'com.diffplug.spotless' version '6.19.0' id 'checkstyle' id 'com.netflix.nebula.ospackage' version "11.3.0" - id "org.gradle.test-retry" version "1.5.2" + id "org.gradle.test-retry" version "1.5.4" id 'eclipse' id "com.github.spotbugs" version "5.0.14" id "com.google.osdetector" version "1.7.3" From 634e3af1f65a918bb66746cd74aa1d7eb4608aa3 Mon Sep 17 00:00:00 2001 From: Heemin Kim Date: Mon, 31 Jul 2023 18:03:24 -0700 Subject: [PATCH 10/12] Integrate geospatial ip2geo functionality with security plugin (#3051) ### Description Adds 2 ip2geo related roles to the plugin: read only and fully access. Adds ip2geo system index to demo scripts as well. Signed-off-by: Heemin Kim --- config/roles.yml | 12 ++++++++++++ tools/install_demo_configuration.bat | 2 +- tools/install_demo_configuration.sh | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/config/roles.yml b/config/roles.yml index bd0e0f6b21..570168fe10 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -108,6 +108,18 @@ knn_full_access: - 'cluster:admin/knn_update_model_graveyard_action' - 'cluster:admin/knn_warmup_action' +# Allow users to execute read only ip2geo datasource action +ip2geo_datasource_read_access: + reserved: true + cluster_permissions: + - 'cluster:admin/geospatial/datasource/get' + +# Allow users to use all ip2geo datasource action +ip2geo_datasource_full_access: + reserved: true + cluster_permissions: + - 'cluster:admin/geospatial/datasource/*' + # Allows users to read Notebooks notebooks_read_access: reserved: true diff --git a/tools/install_demo_configuration.bat b/tools/install_demo_configuration.bat index f648b7bdf5..b0032f318c 100755 --- a/tools/install_demo_configuration.bat +++ b/tools/install_demo_configuration.bat @@ -315,7 +315,7 @@ echo plugins.security.enable_snapshot_restore_privilege: true >> "%OPENSEARCH_CO echo plugins.security.check_snapshot_restore_write_privileges: true >> "%OPENSEARCH_CONF_FILE%" echo plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"] >> "%OPENSEARCH_CONF_FILE%" echo plugins.security.system_indices.enabled: true >> "%OPENSEARCH_CONF_FILE%" -echo plugins.security.system_indices.indices: [".plugins-ml-config", ".plugins-ml-connector", ".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"] >> "%OPENSEARCH_CONF_FILE%" +echo plugins.security.system_indices.indices: [".plugins-ml-config", ".plugins-ml-connector", ".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models", ".geospatial-ip2geo-data*"] >> "%OPENSEARCH_CONF_FILE%" :: network.host >nul findstr /b /c:"network.host" "%OPENSEARCH_CONF_FILE%" && ( diff --git a/tools/install_demo_configuration.sh b/tools/install_demo_configuration.sh index 7cdbe5f2f0..c7a9f57579 100755 --- a/tools/install_demo_configuration.sh +++ b/tools/install_demo_configuration.sh @@ -383,7 +383,7 @@ echo "plugins.security.enable_snapshot_restore_privilege: true" | $SUDO_CMD tee echo "plugins.security.check_snapshot_restore_write_privileges: true" | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null echo 'plugins.security.system_indices.enabled: true' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null -echo 'plugins.security.system_indices.indices: [".plugins-ml-config", ".plugins-ml-connector", ".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null +echo 'plugins.security.system_indices.indices: [".plugins-ml-config", ".plugins-ml-connector", ".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models", ".geospatial-ip2geo-data*"]' | $SUDO_CMD tee -a "$OPENSEARCH_CONF_FILE" > /dev/null #network.host if $SUDO_CMD grep --quiet -i "^network.host" "$OPENSEARCH_CONF_FILE"; then From eac670f286b9d04eb73104937f8e10619efd7673 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Mon, 31 Jul 2023 20:05:13 -0500 Subject: [PATCH 11/12] Update backport version for failure labels (#3063) There have been improvements including adding a label to a pull request when any backport on it has failed. - Related https://github.com/VachaShah/backport/pull/8 Signed-off-by: Peter Nied Signed-off-by: Peter Nied --- .github/workflows/backport.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index e47d8d88c0..ab835cbe9a 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -22,7 +22,9 @@ jobs: installation_id: 22958780 - name: Backport - uses: VachaShah/backport@v1.1.4 + uses: VachaShah/backport@v2.2.0 with: github_token: ${{ steps.github_app_token.outputs.token }} branch_name: backport/backport-${{ github.event.number }} + head_template: backport/backport-<%= number %>-to-<%= base %> + failure_labels: backport-failed From 5384272a63771433905ae409de794786d5747cba Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 1 Aug 2023 14:11:44 -0400 Subject: [PATCH 12/12] Update CircuitBreakerService and LifecycleComponent after core refactor in #9006 (#3082) This PR reacts to changes introduced in core in this PR: https://github.com/opensearch-project/OpenSearch/pull/9006 Signed-off-by: Craig Perkins --- .../opensearch/security/OpenSearchSecurityPlugin.java | 10 +++++----- .../security/ssl/OpenSearchSecuritySSLPlugin.java | 2 +- .../ssl/transport/SecuritySSLNettyTransport.java | 2 +- .../security/test/plugin/UserInjectorPlugin.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e46a04f81f..07865cce67 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -76,10 +76,10 @@ import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.component.Lifecycle.State; -import org.opensearch.common.component.LifecycleComponent; -import org.opensearch.common.component.LifecycleListener; import org.opensearch.common.inject.Inject; +import org.opensearch.common.lifecycle.Lifecycle; +import org.opensearch.common.lifecycle.LifecycleComponent; +import org.opensearch.common.lifecycle.LifecycleListener; import org.opensearch.core.common.io.stream.NamedWriteableRegistry; import org.opensearch.common.logging.DeprecationLogger; import org.opensearch.common.network.NetworkModule; @@ -93,6 +93,7 @@ import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; @@ -104,7 +105,6 @@ import org.opensearch.index.cache.query.QueryCache; import org.opensearch.indices.IndicesService; import org.opensearch.indices.SystemIndexDescriptor; -import org.opensearch.indices.breaker.CircuitBreakerService; import org.opensearch.plugins.ClusterPlugin; import org.opensearch.plugins.MapperPlugin; import org.opensearch.repositories.RepositoriesService; @@ -1938,7 +1938,7 @@ public static ExtensionsManager getExtensionsManager() { public void close() {} @Override - public State lifecycleState() { + public Lifecycle.State lifecycleState() { return null; } diff --git a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java index 358e547470..d7f888dfca 100644 --- a/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java +++ b/src/main/java/org/opensearch/security/ssl/OpenSearchSecuritySSLPlugin.java @@ -56,12 +56,12 @@ import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.HttpServerTransport.Dispatcher; -import org.opensearch.indices.breaker.CircuitBreakerService; import org.opensearch.plugins.NetworkPlugin; import org.opensearch.plugins.Plugin; import org.opensearch.plugins.SystemIndexPlugin; diff --git a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java index 3c3d23022a..ad4ebec1c5 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java +++ b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLNettyTransport.java @@ -53,7 +53,7 @@ import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.PageCacheRecycler; -import org.opensearch.indices.breaker.CircuitBreakerService; +import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.security.ssl.SecurityKeyStore; import org.opensearch.security.ssl.SslExceptionHandler; import org.opensearch.security.ssl.util.SSLConfigConstants; diff --git a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java index 227dd6699d..1046bc81e9 100644 --- a/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java +++ b/src/test/java/org/opensearch/security/test/plugin/UserInjectorPlugin.java @@ -38,11 +38,11 @@ import org.opensearch.common.util.BigArrays; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.HttpServerTransport.Dispatcher; import org.opensearch.http.netty4.Netty4HttpServerTransport; -import org.opensearch.indices.breaker.CircuitBreakerService; import org.opensearch.plugins.NetworkPlugin; import org.opensearch.plugins.Plugin; import org.opensearch.rest.RestChannel;