From d064846416584ee5d84c3e79c9315d7f7a7f2156 Mon Sep 17 00:00:00 2001 From: Aleksandr Maus Date: Mon, 9 Mar 2020 14:11:54 -0400 Subject: [PATCH] EQL: Test infrastructure improvements (#53253) (#53297) Update CommonEqlRestTestCase code to simplify making changes as requested. Update EqlActionIT to simplify the test code as requested. Replace Jackson parser with XContent in EqlActionIT. Whitelist more EQL tests specs that are now supported. --- .../test/eql/CommonEqlRestTestCase.java | 74 ++++------- .../xpack/eql/action/EqlActionIT.java | 65 +++++----- .../resources/test_queries_unsupported.toml | 119 +----------------- 3 files changed, 59 insertions(+), 199 deletions(-) diff --git a/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlRestTestCase.java b/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlRestTestCase.java index f857232c7e9..1b6306fcb2a 100644 --- a/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlRestTestCase.java +++ b/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlRestTestCase.java @@ -16,53 +16,28 @@ import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; -import java.util.ArrayList; - import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; public abstract class CommonEqlRestTestCase extends ESRestTestCase { - static class SearchTestConfiguration { - final String input; - final int expectedStatus; - final String expectedMessage; - - SearchTestConfiguration(String input, int status, String msg) { - this.input = input; - this.expectedStatus = status; - this.expectedMessage = msg; - } - } - - public static final String defaultValidationIndexName = "eql_search_validation_test"; + private static final String defaultValidationIndexName = "eql_search_validation_test"; private static final String validQuery = "process where user = 'SYSTEM'"; - public static final ArrayList searchValidationTests; - static { - searchValidationTests = new ArrayList<>(); - searchValidationTests.add(new SearchTestConfiguration(null, 400, "request body or source parameter is required")); - searchValidationTests.add(new SearchTestConfiguration("{}", 400, "query is null or empty")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"\"}", 400, "query is null or empty")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"timestamp_field\": \"\"}", - 400, "timestamp field is null or empty")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"event_category_field\": \"\"}", - 400, "event category field is null or empty")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"implicit_join_key_field\": \"\"}", - 400, "implicit join key field is null or empty")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"size\": 0}", - 400, "size must be greater than 0")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"size\": -1}", - 400, "size must be greater than 0")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"search_after\": null}", - 400, "search_after doesn't support values of type: VALUE_NULL")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"search_after\": []}", - 400, "must contains at least one value")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"filter\": null}", - 400, "filter doesn't support values of type: VALUE_NULL")); - searchValidationTests.add(new SearchTestConfiguration("{\"query\": \"" + validQuery + "\", \"filter\": {}}", - 400, "query malformed, empty clause found")); - } + private static final String[][] testBadRequests = { + {null, "request body or source parameter is required"}, + {"{}", "query is null or empty"}, + {"{\"query\": \"\"}", "query is null or empty"}, + {"{\"query\": \"" + validQuery + "\", \"timestamp_field\": \"\"}", "timestamp field is null or empty"}, + {"{\"query\": \"" + validQuery + "\", \"event_category_field\": \"\"}", "event category field is null or empty"}, + {"{\"query\": \"" + validQuery + "\", \"implicit_join_key_field\": \"\"}", "implicit join key field is null or empty"}, + {"{\"query\": \"" + validQuery + "\", \"size\": 0}", "size must be greater than 0"}, + {"{\"query\": \"" + validQuery + "\", \"size\": -1}", "size must be greater than 0"}, + {"{\"query\": \"" + validQuery + "\", \"search_after\": null}", "search_after doesn't support values of type: VALUE_NULL"}, + {"{\"query\": \"" + validQuery + "\", \"search_after\": []}", "must contains at least one value"}, + {"{\"query\": \"" + validQuery + "\", \"filter\": null}", "filter doesn't support values of type: VALUE_NULL"}, + {"{\"query\": \"" + validQuery + "\", \"filter\": {}}", "query malformed, empty clause found"} + }; @BeforeClass public static void checkForSnapshot() { @@ -79,24 +54,19 @@ public abstract class CommonEqlRestTestCase extends ESRestTestCase { deleteIndex(defaultValidationIndexName); } - public void testSearchValidationFailures() throws Exception { + public void testBadRequests() throws Exception { final String contentType = "application/json"; - for (SearchTestConfiguration config : searchValidationTests) { + for (String[] test : testBadRequests) { final String endpoint = "/" + defaultValidationIndexName + "/_eql/search"; Request request = new Request("GET", endpoint); - request.setJsonEntity(config.input); + request.setJsonEntity(test[0]); - Response response = null; - if (config.expectedStatus == 400) { - ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); - response = e.getResponse(); - } else { - response = client().performRequest(request); - } + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); + Response response = e.getResponse(); assertThat(response.getHeader("Content-Type"), containsString(contentType)); - assertThat(EntityUtils.toString(response.getEntity()), containsString(config.expectedMessage)); - assertThat(response.getStatusLine().getStatusCode(), is(config.expectedStatus)); + assertThat(EntityUtils.toString(response.getEntity()), containsString(test[1])); + assertThat(response.getStatusLine().getStatusCode(), is(400)); } } } diff --git a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlActionIT.java b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlActionIT.java index 17f9f0da8c6..cf313acd1a1 100644 --- a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlActionIT.java +++ b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlActionIT.java @@ -7,33 +7,32 @@ package org.elasticsearch.xpack.eql.action; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - -import com.fasterxml.jackson.databind.node.ObjectNode; import org.elasticsearch.Build; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.search.SearchHit; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import java.util.ArrayList; -import java.util.Iterator; +import java.util.HashMap; import java.util.List; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; public class EqlActionIT extends AbstractEqlIntegTestCase { static final String indexPrefix = "endgame"; static final String testIndexName = indexPrefix + "-1.4.0"; - protected static final String PARAM_FORMATTING = "%1$s.test"; - + protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s"; @BeforeClass public static void checkForSnapshot() { @@ -41,26 +40,29 @@ public class EqlActionIT extends AbstractEqlIntegTestCase { } @Before + @SuppressWarnings("unchecked") public void setUpData() throws Exception { // Insert test data ObjectMapper mapper = new ObjectMapper(); BulkRequestBuilder bulkBuilder = client().prepareBulk(); - JsonNode rootNode = mapper.readTree(EqlActionIT.class.getResourceAsStream("/test_data.json")); - Iterator entries = rootNode.elements(); - while (entries.hasNext()) { - JsonNode entry = entries.next(); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, EqlActionIT.class.getResourceAsStream("/test_data.json"))) { + List list = parser.list(); + for (Object item : list) { + assertThat(item, instanceOf(HashMap.class)); - // Adjust the structure of the document with additional event.category and @timestamp fields - // Add event.category field - ObjectNode objEvent = ((ObjectNode)entry).putObject("event"); - JsonNode objEventType = entry.get("event_type"); - objEvent.put("category", objEventType.asText()); + HashMap entry = (HashMap) item; - // Add @timestamp field - JsonNode objTimestamp = entry.get("timestamp"); - ((ObjectNode)entry).put("@timestamp", objTimestamp.asLong()); + // Adjust the structure of the document with additional event.category and @timestamp fields + // Add event.category field + HashMap objEvent = new HashMap<>(); + objEvent.put("category", entry.get("event_type")); + entry.put("event", objEvent); - bulkBuilder.add(new IndexRequest(testIndexName).source(entry.toString(), XContentType.JSON)); + // Add @timestamp field + entry.put("@timestamp", entry.get("timestamp")); + + bulkBuilder.add(new IndexRequest(testIndexName).source(entry, XContentType.JSON)); + } } BulkResponse bulkResponse = bulkBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get(); assertThat(bulkResponse.hasFailures() ? bulkResponse.buildFailureMessage() : "", bulkResponse.hasFailures(), equalTo(false)); @@ -108,19 +110,24 @@ public class EqlActionIT extends AbstractEqlIntegTestCase { this.spec = spec; } - public final void test() { - EqlSearchResponse response = new EqlSearchRequestBuilder(client(), EqlSearchAction.INSTANCE) - .indices(testIndexName).query(spec.query()).get(); - - List events = response.hits().events(); - assertNotNull(events); - + private static long[] extractIds(List events) { final int len = events.size(); final long ids[] = new long[len]; - for (int i = 0; i < events.size(); i++) { + for (int i = 0; i < len; i++) { ids[i] = ((Number) events.get(i).getSourceAsMap().get("serial_event_id")).longValue(); } - final String msg = "unexpected result for spec: [" + spec.toString() + "]"; - assertArrayEquals(msg, spec.expectedEventIds(), ids); + return ids; + } + + private void assertSpec(List events) { + assertNotNull(events); + assertArrayEquals("unexpected result for spec: [" + spec.toString() + "]", spec.expectedEventIds(), extractIds(events)); + } + + public final void test() { + EqlSearchResponse response = new EqlSearchRequestBuilder(client(), EqlSearchAction.INSTANCE) + .indices(testIndexName).query(spec.query()).get(); + + assertSpec(response.hits().events()); } } diff --git a/x-pack/plugin/eql/src/test/resources/test_queries_unsupported.toml b/x-pack/plugin/eql/src/test/resources/test_queries_unsupported.toml index f8a96eaef12..b510f80d37f 100644 --- a/x-pack/plugin/eql/src/test/resources/test_queries_unsupported.toml +++ b/x-pack/plugin/eql/src/test/resources/test_queries_unsupported.toml @@ -12,14 +12,11 @@ # query = 'process where serial_event_id = 1' # expected_event_ids = [1] -[[queries]] -query = 'process where serial_event_id < 4' -expected_event_ids = [1, 2, 3] - [[queries]] query = 'process where true | head 6' expected_event_ids = [1, 2, 3, 4, 5, 6] +# presently not supported, throwing: org.elasticsearch.xpack.ql.rule.RuleExecutionException: Does not know how to handle a local relation [[queries]] query = 'process where false' expected_event_ids = [] @@ -32,12 +29,6 @@ query = 'process where missing_field != null' expected_event_ids = [1, 2, 3, 4, 5] query = 'process where bad_field == null | head 5' -[[queries]] -query = ''' - process where process_name == "impossible name" or (serial_event_id < 4.5 and serial_event_id >= 3.1) -''' -expected_event_ids = [4] - [[queries]] tags = ["comparisons", "pipes"] query = ''' @@ -70,42 +61,6 @@ process where true ''' expected_event_ids = [9, 10] -[[queries]] -query = ''' -process where serial_event_id<=8 and serial_event_id > 7 -''' -expected_event_ids = [8] - -[[queries]] -note = "check that comparisons against null values return false" -expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303] -query = 'process where exit_code >= 0' - -[[queries]] -note = "check that comparisons against null values return false" -expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303] -query = 'process where 0 <= exit_code' - -[[queries]] -note = "check that comparisons against null values return false" -expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303] -query = 'process where exit_code <= 0' - -[[queries]] -note = "check that comparisons against null values return false" -expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303] -query = 'process where exit_code < 1' - -[[queries]] -note = "check that comparisons against null values return false" -expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303] -query = 'process where exit_code > -1' - -[[queries]] -note = "check that comparisons against null values return false" -expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303] -query = 'process where -1 < exit_code' - [[queries]] note = "check that comparisons against null values return false" expected_event_ids = [] @@ -125,26 +80,6 @@ note = "check that comparisons against null values return false" expected_event_ids = [1, 2, 3, 4, 5, 6, 7] query = 'process where not (-1 < exit_code) | head 7' -[[queries]] -query = 'process where exit_code > 0' -expected_event_ids = [] - -[[queries]] -query = 'process where exit_code < 0' -expected_event_ids = [] - -[[queries]] -query = 'process where 0 < exit_code' -expected_event_ids = [] - -[[queries]] -query = 'process where 0 > exit_code' -expected_event_ids = [] - -[[queries]] -query = 'process where (serial_event_id<=8 and serial_event_id > 7) and (opcode=3 and opcode>2)' -expected_event_ids = [8] - [[queries]] query = 'process where (serial_event_id<9 and serial_event_id >= 7) or (opcode == pid)' expected_event_ids = [7, 8] @@ -217,16 +152,6 @@ expected_event_ids = [57] query = ''' registry where length(bytes_written_string_list) == 2 and bytes_written_string_list[1] == "EN"''' -[[queries]] -query = ''' -registry where key_path == "*\\MACHINE\\SAM\\SAM\\*\\Account\\Us*ers\\00*03E9\\F"''' -expected_event_ids = [79] - -[[queries]] -query = ''' -process where process_path == "*\\red_ttp\\wininit.*" and opcode in (0,1,2,3,4)''' -expected_event_ids = [84, 85] - [[queries]] query = ''' file where file_name == "csrss.exe" and opcode=0 @@ -253,34 +178,6 @@ process where opcode=1 and process_name == "smss.exe" ''' expected_event_ids = [78] -[[queries]] -query = ''' -file where file_path="*\\red_ttp\\winin*.*" - and opcode in (0,1,2) and user_name="vagrant" -''' -expected_event_ids = [83, 86] - -[[queries]] -query = ''' -file where file_path="*\\red_ttp\\winin*.*" - and opcode not in (0,1,2) and user_name="vagrant" -''' -expected_event_ids = [] - -[[queries]] -query = ''' -file where file_path="*\\red_ttp\\winin*.*" - and opcode not in (3, 4, 5, 6 ,7) and user_name="vagrant" -''' -expected_event_ids = [83, 86] - - -[[queries]] -query = ''' -file where file_name in ("wininit.exe", "lsass.exe") and opcode == 2 -''' -expected_event_ids = [65, 86] - [[queries]] query = ''' file where true @@ -612,15 +509,6 @@ query = ''' registry where length(bad_field) > 0 ''' -[[queries]] -query = ''' -process where opcode == 1 - and process_name in ("net.exe", "net1.exe") - and not (parent_process_name == "net.exe" - and process_name == "net1.exe") - and command_line == "*group *admin*" and command_line != "* /add*"''' -expected_event_ids = [97] - [[queries]] expected_event_ids = [1, 55, 57, 63, 75304] query = ''' @@ -690,11 +578,6 @@ file where event of [process where process_name = "python.exe" ] | unique unique_pid''' expected_event_ids = [55, 95] -[[queries]] -query = ''' -process where process_name = "python.exe"''' -expected_event_ids = [48, 50, 51, 54, 93] - [[queries]] query = 'process where event of [process where process_name = "python.exe" ]' expected_event_ids = [48, 50, 51, 54, 93]