Skip to content

Commit

Permalink
Rule field validation during IndexRule Action (#80)
Browse files Browse the repository at this point in the history
Signed-off-by: Petar Dzepina <petar.dzepina@gmail.com>
(cherry picked from commit bfb2b23)
  • Loading branch information
petardz authored and github-actions[bot] committed Nov 3, 2022
1 parent b37e017 commit 2f92260
Show file tree
Hide file tree
Showing 19 changed files with 891 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
import org.opensearch.securityanalytics.action.IndexDetectorAction;
import org.opensearch.securityanalytics.action.SearchDetectorAction;
import org.opensearch.securityanalytics.action.UpdateIndexMappingsAction;
import org.opensearch.securityanalytics.action.ValidateRulesAction;
import org.opensearch.securityanalytics.mapper.MapperService;
import org.opensearch.securityanalytics.resthandler.RestAcknowledgeAlertsAction;
import org.opensearch.securityanalytics.resthandler.RestGetFindingsAction;
import org.opensearch.securityanalytics.resthandler.RestValidateRulesAction;
import org.opensearch.securityanalytics.transport.TransportAcknowledgeAlertsAction;
import org.opensearch.securityanalytics.transport.TransportCreateIndexMappingsAction;
import org.opensearch.securityanalytics.transport.TransportGetFindingsAction;
Expand Down Expand Up @@ -75,6 +77,7 @@
import org.opensearch.securityanalytics.transport.TransportGetMappingsViewAction;
import org.opensearch.securityanalytics.transport.TransportIndexDetectorAction;
import org.opensearch.securityanalytics.transport.TransportSearchDetectorAction;
import org.opensearch.securityanalytics.transport.TransportValidateRulesAction;
import org.opensearch.securityanalytics.util.DetectorIndices;
import org.opensearch.securityanalytics.util.RuleIndices;
import org.opensearch.securityanalytics.util.RuleTopicIndices;
Expand Down Expand Up @@ -140,7 +143,8 @@ public List<RestHandler> getRestHandlers(Settings settings,
new RestGetAlertsAction(),
new RestIndexRuleAction(),
new RestSearchRuleAction(),
new RestDeleteRuleAction()
new RestDeleteRuleAction(),
new RestValidateRulesAction()
);
}

Expand Down Expand Up @@ -176,7 +180,8 @@ public List<Setting<?>> getSettings() {
new ActionPlugin.ActionHandler<>(GetAlertsAction.INSTANCE, TransportGetAlertsAction.class),
new ActionPlugin.ActionHandler<>(IndexRuleAction.INSTANCE, TransportIndexRuleAction.class),
new ActionPlugin.ActionHandler<>(SearchRuleAction.INSTANCE, TransportSearchRuleAction.class),
new ActionPlugin.ActionHandler<>(DeleteRuleAction.INSTANCE, TransportDeleteRuleAction.class)
new ActionPlugin.ActionHandler<>(DeleteRuleAction.INSTANCE, TransportDeleteRuleAction.class),
new ActionPlugin.ActionHandler<>(ValidateRulesAction.INSTANCE, TransportValidateRulesAction.class)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.securityanalytics.action;

import org.opensearch.action.ActionType;
import org.opensearch.action.support.master.AcknowledgedResponse;

public class ValidateRulesAction extends ActionType<ValidateRulesResponse>{

public static final String NAME = "cluster:admin/opendistro/securityanalytics/rules/validate";
public static final ValidateRulesAction INSTANCE = new ValidateRulesAction();


public ValidateRulesAction() {
super(NAME, ValidateRulesResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.securityanalytics.action;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.common.Strings;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.xcontent.ToXContentObject;
import org.opensearch.common.xcontent.XContentBuilder;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.common.xcontent.XContentParser;
import org.opensearch.common.xcontent.XContentParserUtils;


import static org.opensearch.action.ValidateActions.addValidationError;

public class ValidateRulesRequest extends ActionRequest implements ToXContentObject {

private static final Logger log = LogManager.getLogger(ValidateRulesRequest.class);

public static final String INDEX_NAME_FIELD = "index_name";
public static final String RULES_FIELD = "rules";

String indexName;
List<String> rules;

public ValidateRulesRequest(String indexName, List<String> rules) {
super();
this.indexName = indexName;
this.rules = rules;
}

public ValidateRulesRequest(StreamInput sin) throws IOException {
this(
sin.readString(),
sin.readStringList()
);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (indexName == null || indexName.length() == 0) {
validationException = addValidationError(String.format(Locale.getDefault(), "%s is missing", INDEX_NAME_FIELD), validationException);
}
if (rules == null || rules.size() == 0) {
validationException = addValidationError(String.format(Locale.getDefault(), "%s are missing", RULES_FIELD), validationException);
}
return validationException;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(indexName);
out.writeStringCollection(rules);
}

public static ValidateRulesRequest parse(XContentParser xcp) throws IOException {
String indexName = null;
List<String> ruleIds = null;

if (xcp.currentToken() == null) {
xcp.nextToken();
}
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp);
while (xcp.nextToken() != XContentParser.Token.END_OBJECT) {
String fieldName = xcp.currentName();
xcp.nextToken();

switch (fieldName) {
case INDEX_NAME_FIELD:
indexName = xcp.text();
break;
case RULES_FIELD:
ruleIds = new ArrayList<>();
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp);
while (xcp.nextToken() != XContentParser.Token.END_ARRAY) {
ruleIds.add(xcp.text());
}
break;
default:
xcp.skipChildren();
}
}
return new ValidateRulesRequest(indexName, ruleIds);
}

public ValidateRulesRequest indexName(String indexName) {
this.indexName = indexName;
return this;
}

public ValidateRulesRequest rules(List<String> rules) {
this.rules = rules;
return this;
}



public String getIndexName() {
return this.indexName;
}

public List<String> getRules() {
return this.rules;
}

public void setIndexName(String indexName) {
this.indexName = indexName;
}

public List<String> setRules(List<String> rules) {
return this.rules = rules;
}


@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(INDEX_NAME_FIELD, indexName)
.field(RULES_FIELD, rules)
.endObject();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.securityanalytics.action;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.opensearch.action.ActionResponse;
import org.opensearch.common.Strings;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.xcontent.ToXContentObject;
import org.opensearch.common.xcontent.XContentBuilder;
import org.opensearch.securityanalytics.mapper.MapperUtils;

public class ValidateRulesResponse extends ActionResponse implements ToXContentObject {

public static final String NONAPPLICABLE_FIELDS = "nonapplicable_fields";

List<String> nonapplicableFields;

public ValidateRulesResponse(List<String> nonapplicableFields) {
this.nonapplicableFields = nonapplicableFields;
}

public ValidateRulesResponse(StreamInput in) throws IOException {
super(in);
nonapplicableFields = in.readStringList();
int size = in.readVInt();
if (size > 0) {
nonapplicableFields = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
nonapplicableFields.add(in.readString());
}
}
}

@Override
public void writeTo(StreamOutput out) throws IOException {
if (nonapplicableFields != null) {
out.writeVInt(nonapplicableFields.size());
for (String f : nonapplicableFields) {
out.writeString(f);
}
} else {
out.writeVInt(0);
}
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (nonapplicableFields != null && nonapplicableFields.size() > 0) {
builder.field(NONAPPLICABLE_FIELDS, nonapplicableFields);
}
return builder.endObject();
}

public List<String> getNonapplicableFields() {
return nonapplicableFields;
}

@Override
public String toString() {
return Strings.toString(this);
}

@Override
public int hashCode() {
return Objects.hash(new Object[]{this.nonapplicableFields});
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}

if (getClass() != obj.getClass()) {
return false;
}
ValidateRulesResponse other = (ValidateRulesResponse) obj;
return this.nonapplicableFields.equals(other.nonapplicableFields);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,51 @@ public static List<String> validateIndexMappings(ImmutableOpenMap<String, Mappin
.collect(Collectors.toList());
}

/**
* Traverses mappings tree and collects all fields.
* Nested fields are flattened.
* @return list of fields in mappings.
*/
public static List<String> extractAllFieldsFlat(MappingMetadata mappingMetadata) {
MappingsTraverser mappingsTraverser = new MappingsTraverser(mappingMetadata);
List<String> flatProperties = new ArrayList<>();
// Setup
mappingsTraverser.addListener(new MappingsTraverser.MappingsTraverserListener() {
@Override
public void onLeafVisited(MappingsTraverser.Node node) {
flatProperties.add(node.currentPath);
}

@Override
public void onError(String error) {
throw new IllegalArgumentException(error);
}
});
// Do traverse
mappingsTraverser.traverse();
return flatProperties;
}

public static List<String> extractAllFieldsFlat(Map<String, Object> mappingsMap) {
MappingsTraverser mappingsTraverser = new MappingsTraverser(mappingsMap, Set.of());
List<String> flatProperties = new ArrayList<>();
// Setup
mappingsTraverser.addListener(new MappingsTraverser.MappingsTraverserListener() {
@Override
public void onLeafVisited(MappingsTraverser.Node node) {
flatProperties.add(node.currentPath);
}

@Override
public void onError(String error) {
throw new IllegalArgumentException(error);
}
});
// Do traverse
mappingsTraverser.traverse();
return flatProperties;
}

public static List<String> getAllNonAliasFieldsFromIndex(MappingMetadata mappingMetadata) {
MappingsTraverser mappingsTraverser = new MappingsTraverser(mappingMetadata);
return mappingsTraverser.extractFlatNonAliasFields();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class Detector implements Writeable, ToXContentObject {
private static final String FINDINGS_INDEX = "findings_index";
private static final String FINDINGS_INDEX_PATTERN = "findings_index_pattern";

public static final String DETECTORS_INDEX = ".opensearch-detectors-config";
public static final String DETECTORS_INDEX = ".opensearch-sap-detectors-config";

public static final NamedXContentRegistry.Entry XCONTENT_REGISTRY = new NamedXContentRegistry.Entry(
Detector.class,
Expand Down
Loading

0 comments on commit 2f92260

Please sign in to comment.