From 461f9f65ccb5e3e9444d2ce69013fd9856f49b30 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Wed, 13 Oct 2021 21:12:21 -0400 Subject: [PATCH 01/31] fix package.json: (#114936) --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 6e4a37863bc82..f526f357ff347 100644 --- a/package.json +++ b/package.json @@ -91,9 +91,6 @@ "yarn": "^1.21.1" }, "dependencies": { - "@dnd-kit/core": "^3.1.1", - "@dnd-kit/sortable": "^4.0.0", - "@dnd-kit/utilities": "^2.0.0", "@babel/runtime": "^7.15.4", "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", From 5647de3b4a32815a5c8c4df1a35072bb3d1db7db Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 13 Oct 2021 18:18:06 -0700 Subject: [PATCH 02/31] [ci] Fixes Bazel cache writes (#114915) Signed-off-by: Tyler Smalley --- .../scripts/common/persist_bazel_cache.sh | 14 +++++++++++ .buildkite/scripts/common/setup_bazel.sh | 24 ------------------- .../steps/on_merge_build_and_metrics.sh | 2 +- src/dev/ci_setup/.bazelrc-ci | 13 +++++----- src/dev/ci_setup/.bazelrc-ci.common | 11 --------- 5 files changed, 21 insertions(+), 43 deletions(-) create mode 100755 .buildkite/scripts/common/persist_bazel_cache.sh delete mode 100755 .buildkite/scripts/common/setup_bazel.sh delete mode 100644 src/dev/ci_setup/.bazelrc-ci.common diff --git a/.buildkite/scripts/common/persist_bazel_cache.sh b/.buildkite/scripts/common/persist_bazel_cache.sh new file mode 100755 index 0000000000000..597ab0947c267 --- /dev/null +++ b/.buildkite/scripts/common/persist_bazel_cache.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +source .buildkite/scripts/common/util.sh + +KIBANA_BUILDBUDDY_CI_API_KEY=$(retry 5 5 vault read -field=value secret/kibana-issues/dev/kibana-buildbuddy-ci-api-key) +export KIBANA_BUILDBUDDY_CI_API_KEY + +cp "$KIBANA_DIR/src/dev/ci_setup/.bazelrc-ci" "$KIBANA_DIR/.bazelrc" + +### +### append auth token to buildbuddy into "$HOME/.bazelrc"; +### +echo "# Appended by .buildkite/scripts/persist_bazel_cache.sh" >> "$KIBANA_DIR/.bazelrc" +echo "build --remote_header=x-buildbuddy-api-key=$KIBANA_BUILDBUDDY_CI_API_KEY" >> "$KIBANA_DIR/.bazelrc" diff --git a/.buildkite/scripts/common/setup_bazel.sh b/.buildkite/scripts/common/setup_bazel.sh deleted file mode 100755 index bbd1c58497172..0000000000000 --- a/.buildkite/scripts/common/setup_bazel.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -source .buildkite/scripts/common/util.sh - -KIBANA_BUILDBUDDY_CI_API_KEY=$(retry 5 5 vault read -field=value secret/kibana-issues/dev/kibana-buildbuddy-ci-api-key) -export KIBANA_BUILDBUDDY_CI_API_KEY - -cp "$KIBANA_DIR/src/dev/ci_setup/.bazelrc-ci" "$HOME/.bazelrc" - -### -### append auth token to buildbuddy into "$HOME/.bazelrc"; -### -echo "# Appended by .buildkite/scripts/setup_bazel.sh" >> "$HOME/.bazelrc" -echo "build --remote_header=x-buildbuddy-api-key=$KIBANA_BUILDBUDDY_CI_API_KEY" >> "$HOME/.bazelrc" - -### -### remove write permissions on buildbuddy remote cache for prs -### -if [[ "${BUILDKITE_PULL_REQUEST:-}" && "$BUILDKITE_PULL_REQUEST" != "false" ]] ; then - { - echo "# Uploads logs & artifacts without writing to cache" - echo "build --noremote_upload_local_results" - } >> "$HOME/.bazelrc" -fi diff --git a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh index b24e585e70735..315ba08f8719b 100755 --- a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh +++ b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh @@ -3,7 +3,7 @@ set -euo pipefail # Write Bazel cache for Linux -.buildkite/scripts/common/setup_bazel.sh +.buildkite/scripts/common/persist_bazel_cache.sh .buildkite/scripts/bootstrap.sh .buildkite/scripts/build_kibana.sh diff --git a/src/dev/ci_setup/.bazelrc-ci b/src/dev/ci_setup/.bazelrc-ci index ef6fab3a30590..9aee657f37bcb 100644 --- a/src/dev/ci_setup/.bazelrc-ci +++ b/src/dev/ci_setup/.bazelrc-ci @@ -1,16 +1,15 @@ -# Inspired on https://github.com/angular/angular/blob/master/.circleci/bazel.linux.rc -# These options are only enabled when running on CI -# That is done by copying this file into "$HOME/.bazelrc" which loads after the .bazelrc into the workspace +# Used in the on-merge job to persist the Bazel cache to BuildBuddy +# from: .buildkite/scripts/common/persist_bazel_cache.sh -# Import and load bazelrc common settings for ci env -try-import %workspace%/src/dev/ci_setup/.bazelrc-ci.common +import %workspace%/.bazelrc.common # BuildBuddy settings -## Remote settings including cache build --bes_results_url=https://app.buildbuddy.io/invocation/ build --bes_backend=grpcs://cloud.buildbuddy.io build --remote_cache=grpcs://cloud.buildbuddy.io build --remote_timeout=3600 +# --remote_header=x-buildbuddy-api-key= # appended in CI script -## Metadata settings +# Metadata settings build --build_metadata=ROLE=CI +build --workspace_status_command="node ./src/dev/bazel_workspace_status.js" diff --git a/src/dev/ci_setup/.bazelrc-ci.common b/src/dev/ci_setup/.bazelrc-ci.common deleted file mode 100644 index 56a5ee8d30cd6..0000000000000 --- a/src/dev/ci_setup/.bazelrc-ci.common +++ /dev/null @@ -1,11 +0,0 @@ -# Inspired on https://github.com/angular/angular/blob/master/.circleci/bazel.common.rc -# Settings in this file should be OS agnostic - -# Don't be spammy in the logs -build --noshow_progress - -# More details on failures -build --verbose_failures=true - -## Avoid to keep connections to build event backend connections alive across builds -build --keep_backend_build_event_connections_alive=false From 423b0e801fb29dcf02f6588fa8f888b9aebe2f15 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 14 Oct 2021 03:35:17 +0100 Subject: [PATCH 03/31] chore(NA): fixes a typo on persist_bazel_cache.sh comment (#114943) --- .buildkite/scripts/common/persist_bazel_cache.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.buildkite/scripts/common/persist_bazel_cache.sh b/.buildkite/scripts/common/persist_bazel_cache.sh index 597ab0947c267..357805c11acec 100755 --- a/.buildkite/scripts/common/persist_bazel_cache.sh +++ b/.buildkite/scripts/common/persist_bazel_cache.sh @@ -5,10 +5,11 @@ source .buildkite/scripts/common/util.sh KIBANA_BUILDBUDDY_CI_API_KEY=$(retry 5 5 vault read -field=value secret/kibana-issues/dev/kibana-buildbuddy-ci-api-key) export KIBANA_BUILDBUDDY_CI_API_KEY +# overwrites the file checkout .bazelrc file with the one intended for CI env cp "$KIBANA_DIR/src/dev/ci_setup/.bazelrc-ci" "$KIBANA_DIR/.bazelrc" ### -### append auth token to buildbuddy into "$HOME/.bazelrc"; +### append auth token to buildbuddy into "$KIBANA_DIR/.bazelrc"; ### echo "# Appended by .buildkite/scripts/persist_bazel_cache.sh" >> "$KIBANA_DIR/.bazelrc" echo "build --remote_header=x-buildbuddy-api-key=$KIBANA_BUILDBUDDY_CI_API_KEY" >> "$KIBANA_DIR/.bazelrc" From 86f0733e5642a10b77025f36199d895282c1e601 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 13 Oct 2021 19:49:16 -0700 Subject: [PATCH 04/31] Clean up inaccurate comments (#114935) --- .../create_package_policy_page/step_configure_package.tsx | 1 - x-pack/plugins/fleet/server/services/epm/packages/get.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index 1ff5d20baec06..390e540f1b10f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -49,7 +49,6 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ ); // Configure inputs (and their streams) - // Assume packages only export one config template for now const renderConfigureInputs = () => packagePolicyTemplates.length ? ( <> diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index cf847cdf62bc2..8d3c1fbe0daa4 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -69,7 +69,6 @@ export async function getPackages( } // Get package names for packages which cannot have more than one package policy on an agent policy -// Assume packages only export one policy template for now export async function getLimitedPackages(options: { savedObjectsClient: SavedObjectsClientContract; }): Promise { From 69a6cf329ce8be83b48a102e28983b7d0ca11ab8 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 13 Oct 2021 20:32:43 -0700 Subject: [PATCH 05/31] Fixing exceptions export format (#114920) ### Summary Fixing exceptions export format and adding integration tests for it. --- .../src/api/index.ts | 2 +- .../kbn-securitysolution-utils/src/index.ts | 1 + .../transform_data_to_ndjson/index.test.ts | 88 ++++++++++ .../src/transform_data_to_ndjson/index.ts | 16 ++ .../lists/public/exceptions/api.test.ts | 2 +- .../routes/export_exception_list_route.ts | 42 +++-- .../downloads/test_exception_list.ndjson | 2 + .../exceptions/exceptions_table.spec.ts | 2 +- .../cypress/objects/exception.ts | 3 +- .../detection_engine/rules/get_export_all.ts | 3 +- .../rules/get_export_by_object_ids.ts | 2 +- .../server/lib/telemetry/sender.ts | 3 +- .../timelines/export_timelines/helpers.ts | 3 +- .../create_stream_from_ndjson.test.ts | 71 -------- .../read_stream/create_stream_from_ndjson.ts | 9 - .../tests/export_exception_list.ts | 155 ++++++++++++++++++ .../security_and_spaces/tests/index.ts | 1 + 17 files changed, 292 insertions(+), 113 deletions(-) create mode 100644 packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts create mode 100644 packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.ts create mode 100644 x-pack/plugins/security_solution/cypress/downloads/test_exception_list.ndjson delete mode 100644 x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts create mode 100644 x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts diff --git a/packages/kbn-securitysolution-list-api/src/api/index.ts b/packages/kbn-securitysolution-list-api/src/api/index.ts index d70417a29971f..77c50fb32c299 100644 --- a/packages/kbn-securitysolution-list-api/src/api/index.ts +++ b/packages/kbn-securitysolution-list-api/src/api/index.ts @@ -558,7 +558,7 @@ export const exportExceptionList = async ({ signal, }: ExportExceptionListProps): Promise => http.fetch(`${EXCEPTION_LIST_URL}/_export`, { - method: 'GET', + method: 'POST', query: { id, list_id: listId, namespace_type: namespaceType }, signal, }); diff --git a/packages/kbn-securitysolution-utils/src/index.ts b/packages/kbn-securitysolution-utils/src/index.ts index 0bb36c590ffdf..755bbd2203dff 100644 --- a/packages/kbn-securitysolution-utils/src/index.ts +++ b/packages/kbn-securitysolution-utils/src/index.ts @@ -7,3 +7,4 @@ */ export * from './add_remove_id_to_item'; +export * from './transform_data_to_ndjson'; diff --git a/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts b/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts new file mode 100644 index 0000000000000..b10626357f5b1 --- /dev/null +++ b/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { transformDataToNdjson } from './'; + +export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; + +const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE) => ({ + author: [], + id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', + created_at: new Date(anchorDate).toISOString(), + updated_at: new Date(anchorDate).toISOString(), + created_by: 'elastic', + description: 'some description', + enabled: true, + false_positives: ['false positive 1', 'false positive 2'], + from: 'now-6m', + immutable: false, + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + references: ['test 1', 'test 2'], + severity: 'high', + severity_mapping: [], + updated_by: 'elastic_kibana', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + threat: [], + version: 1, + status: 'succeeded', + status_date: '2020-02-22T16:47:50.047Z', + last_success_at: '2020-02-22T16:47:50.047Z', + last_success_message: 'succeeded', + output_index: '.siem-signals-default', + max_signals: 100, + risk_score: 55, + risk_score_mapping: [], + language: 'kuery', + rule_id: 'query-rule-id', + interval: '5m', + exceptions_list: [], +}); + +describe('transformDataToNdjson', () => { + test('if rules are empty it returns an empty string', () => { + const ruleNdjson = transformDataToNdjson([]); + expect(ruleNdjson).toEqual(''); + }); + + test('single rule will transform with new line ending character for ndjson', () => { + const rule = getRulesSchemaMock(); + const ruleNdjson = transformDataToNdjson([rule]); + expect(ruleNdjson.endsWith('\n')).toBe(true); + }); + + test('multiple rules will transform with two new line ending characters for ndjson', () => { + const result1 = getRulesSchemaMock(); + const result2 = getRulesSchemaMock(); + result2.id = 'some other id'; + result2.rule_id = 'some other id'; + result2.name = 'Some other rule'; + + const ruleNdjson = transformDataToNdjson([result1, result2]); + // this is how we count characters in JavaScript :-) + const count = ruleNdjson.split('\n').length - 1; + expect(count).toBe(2); + }); + + test('you can parse two rules back out without errors', () => { + const result1 = getRulesSchemaMock(); + const result2 = getRulesSchemaMock(); + result2.id = 'some other id'; + result2.rule_id = 'some other id'; + result2.name = 'Some other rule'; + + const ruleNdjson = transformDataToNdjson([result1, result2]); + const ruleStrings = ruleNdjson.split('\n'); + const reParsed1 = JSON.parse(ruleStrings[0]); + const reParsed2 = JSON.parse(ruleStrings[1]); + expect(reParsed1).toEqual(result1); + expect(reParsed2).toEqual(result2); + }); +}); diff --git a/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.ts b/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.ts new file mode 100644 index 0000000000000..66a500731f497 --- /dev/null +++ b/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const transformDataToNdjson = (data: unknown[]): string => { + if (data.length !== 0) { + const dataString = data.map((item) => JSON.stringify(item)).join('\n'); + return `${dataString}\n`; + } else { + return ''; + } +}; diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts index a196999d14943..65c11bfc1dfd0 100644 --- a/x-pack/plugins/lists/public/exceptions/api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/api.test.ts @@ -759,7 +759,7 @@ describe('Exceptions Lists API', () => { }); expect(httpMock.fetch).toHaveBeenCalledWith('/api/exception_lists/_export', { - method: 'GET', + method: 'POST', query: { id: 'some-id', list_id: 'list-id', diff --git a/x-pack/plugins/lists/server/routes/export_exception_list_route.ts b/x-pack/plugins/lists/server/routes/export_exception_list_route.ts index a238d0e6529ff..aa30c8a7d435d 100644 --- a/x-pack/plugins/lists/server/routes/export_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/export_exception_list_route.ts @@ -6,6 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import { exportExceptionListQuerySchema } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; @@ -14,7 +15,7 @@ import type { ListsPluginRouter } from '../types'; import { buildRouteValidation, buildSiemResponse, getExceptionListClient } from './utils'; export const exportExceptionListRoute = (router: ListsPluginRouter): void => { - router.get( + router.post( { options: { tags: ['access:lists-read'], @@ -26,6 +27,7 @@ export const exportExceptionListRoute = (router: ListsPluginRouter): void => { }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); + try { const { id, list_id: listId, namespace_type: namespaceType } = request.query; const exceptionLists = getExceptionListClient(context); @@ -37,11 +39,10 @@ export const exportExceptionListRoute = (router: ListsPluginRouter): void => { if (exceptionList == null) { return siemResponse.error({ - body: `list_id: ${listId} does not exist`, + body: `exception list with list_id: ${listId} does not exist`, statusCode: 400, }); } else { - const { exportData: exportList } = getExport([exceptionList]); const listItems = await exceptionLists.findExceptionListItem({ filter: undefined, listId, @@ -51,19 +52,15 @@ export const exportExceptionListRoute = (router: ListsPluginRouter): void => { sortField: 'exception-list.created_at', sortOrder: 'desc', }); + const exceptionItems = listItems?.data ?? []; - const { exportData: exportListItems, exportDetails } = getExport(listItems?.data ?? []); - - const responseBody = [ - exportList, - exportListItems, - { exception_list_items_details: exportDetails }, - ]; + const { exportData } = getExport([exceptionList, ...exceptionItems]); + const { exportDetails } = getExportDetails(exceptionItems); // TODO: Allow the API to override the name of the file to export const fileName = exceptionList.list_id; return response.ok({ - body: transformDataToNdjson(responseBody), + body: `${exportData}${exportDetails}`, headers: { 'Content-Disposition': `attachment; filename="${fileName}"`, 'Content-Type': 'application/ndjson', @@ -81,24 +78,23 @@ export const exportExceptionListRoute = (router: ListsPluginRouter): void => { ); }; -const transformDataToNdjson = (data: unknown[]): string => { - if (data.length !== 0) { - const dataString = data.map((dataItem) => JSON.stringify(dataItem)).join('\n'); - return `${dataString}\n`; - } else { - return ''; - } -}; - export const getExport = ( data: unknown[] ): { exportData: string; - exportDetails: string; } => { const ndjson = transformDataToNdjson(data); + + return { exportData: ndjson }; +}; + +export const getExportDetails = ( + items: unknown[] +): { + exportDetails: string; +} => { const exportDetails = JSON.stringify({ - exported_count: data.length, + exported_list_items_count: items.length, }); - return { exportData: ndjson, exportDetails: `${exportDetails}\n` }; + return { exportDetails: `${exportDetails}\n` }; }; diff --git a/x-pack/plugins/security_solution/cypress/downloads/test_exception_list.ndjson b/x-pack/plugins/security_solution/cypress/downloads/test_exception_list.ndjson new file mode 100644 index 0000000000000..54420eff29e0d --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/downloads/test_exception_list.ndjson @@ -0,0 +1,2 @@ +{"_version":"WzQyNjA0LDFd","created_at":"2021-10-14T01:30:22.034Z","created_by":"elastic","description":"Test exception list description","id":"4c65a230-2c8e-11ec-be1c-2bbdec602f88","immutable":false,"list_id":"test_exception_list","name":"Test exception list","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"b04983b4-1617-441c-bb6c-c729281fa2e9","type":"detection","updated_at":"2021-10-14T01:30:22.036Z","updated_by":"elastic","version":1} +{"exported_list_items_count":0} diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts index 8530f949664b8..c8b6f73912acf 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts @@ -81,7 +81,7 @@ describe('Exceptions Table', () => { cy.wait('@export').then(({ response }) => cy - .wrap(response?.body!) + .wrap(response?.body) .should('eql', expectedExportedExceptionList(this.exceptionListResponse)) ); }); diff --git a/x-pack/plugins/security_solution/cypress/objects/exception.ts b/x-pack/plugins/security_solution/cypress/objects/exception.ts index 81c3b885ab94d..b772924697148 100644 --- a/x-pack/plugins/security_solution/cypress/objects/exception.ts +++ b/x-pack/plugins/security_solution/cypress/objects/exception.ts @@ -41,6 +41,5 @@ export const expectedExportedExceptionList = ( exceptionListResponse: Cypress.Response ): string => { const jsonrule = exceptionListResponse.body; - - return `"{\\"_version\\":\\"${jsonrule._version}\\",\\"created_at\\":\\"${jsonrule.created_at}\\",\\"created_by\\":\\"elastic\\",\\"description\\":\\"${jsonrule.description}\\",\\"id\\":\\"${jsonrule.id}\\",\\"immutable\\":false,\\"list_id\\":\\"test_exception_list\\",\\"name\\":\\"Test exception list\\",\\"namespace_type\\":\\"single\\",\\"os_types\\":[],\\"tags\\":[],\\"tie_breaker_id\\":\\"${jsonrule.tie_breaker_id}\\",\\"type\\":\\"detection\\",\\"updated_at\\":\\"${jsonrule.updated_at}\\",\\"updated_by\\":\\"elastic\\",\\"version\\":1}\\n"\n""\n{"exception_list_items_details":"{\\"exported_count\\":0}\\n"}\n`; + return `{"_version":"${jsonrule._version}","created_at":"${jsonrule.created_at}","created_by":"elastic","description":"${jsonrule.description}","id":"${jsonrule.id}","immutable":false,"list_id":"test_exception_list","name":"Test exception list","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonrule.tie_breaker_id}","type":"detection","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","version":1}\n{"exported_list_items_count":0}\n`; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts index f44471e6e26f9..71079ccefc97a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts @@ -5,11 +5,12 @@ * 2.0. */ +import { transformDataToNdjson } from '@kbn/securitysolution-utils'; + import { RulesClient } from '../../../../../alerting/server'; import { getNonPackagedRules } from './get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { transformAlertsToRules } from '../routes/rules/utils'; -import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson'; export const getExportAll = async ( rulesClient: RulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts index 31a7604306de7..4cf3ad9133a71 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -6,13 +6,13 @@ */ import { chunk } from 'lodash'; +import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { RulesClient } from '../../../../../alerting/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../rules/types'; import { transformAlertToRule } from '../routes/rules/utils'; -import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { findRules } from './find_rules'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 2966fa3decc26..7c9906d0eae48 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -8,10 +8,11 @@ import { cloneDeep } from 'lodash'; import axios from 'axios'; import { URL } from 'url'; +import { transformDataToNdjson } from '@kbn/securitysolution-utils'; + import { Logger } from 'src/core/server'; import { TelemetryPluginStart, TelemetryPluginSetup } from 'src/plugins/telemetry/server'; import { UsageCounter } from 'src/plugins/usage_collection/server'; -import { transformDataToNdjson } from '../../utils/read_stream/create_stream_from_ndjson'; import { TaskManagerSetupContract, TaskManagerStartContract, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts index a33b8be0c2f31..c857e7fa38a27 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts @@ -6,6 +6,7 @@ */ import { omit } from 'lodash/fp'; +import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import { ExportedTimelines, @@ -15,8 +16,6 @@ import { import { NoteSavedObject } from '../../../../../../common/types/timeline/note'; import { PinnedEventSavedObject } from '../../../../../../common/types/timeline/pinned_event'; -import { transformDataToNdjson } from '../../../../../utils/read_stream/create_stream_from_ndjson'; - import { FrameworkRequest } from '../../../../framework'; import * as noteLib from '../../../saved_object/notes'; import * as pinnedEventLib from '../../../saved_object/pinned_events'; diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts deleted file mode 100644 index c3163da6ac949..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { transformDataToNdjson } from './create_stream_from_ndjson'; -import { ImportRulesSchemaDecoded } from '../../../common/detection_engine/schemas/request/import_rules_schema'; -import { getRulesSchemaMock } from '../../../common/detection_engine/schemas/response/rules_schema.mocks'; - -export const getOutputSample = (): Partial => ({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', -}); - -export const getSampleAsNdjson = (sample: Partial): string => { - return `${JSON.stringify(sample)}\n`; -}; - -describe('create_rules_stream_from_ndjson', () => { - describe('transformDataToNdjson', () => { - test('if rules are empty it returns an empty string', () => { - const ruleNdjson = transformDataToNdjson([]); - expect(ruleNdjson).toEqual(''); - }); - - test('single rule will transform with new line ending character for ndjson', () => { - const rule = getRulesSchemaMock(); - const ruleNdjson = transformDataToNdjson([rule]); - expect(ruleNdjson.endsWith('\n')).toBe(true); - }); - - test('multiple rules will transform with two new line ending characters for ndjson', () => { - const result1 = getRulesSchemaMock(); - const result2 = getRulesSchemaMock(); - result2.id = 'some other id'; - result2.rule_id = 'some other id'; - result2.name = 'Some other rule'; - - const ruleNdjson = transformDataToNdjson([result1, result2]); - // this is how we count characters in JavaScript :-) - const count = ruleNdjson.split('\n').length - 1; - expect(count).toBe(2); - }); - - test('you can parse two rules back out without errors', () => { - const result1 = getRulesSchemaMock(); - const result2 = getRulesSchemaMock(); - result2.id = 'some other id'; - result2.rule_id = 'some other id'; - result2.name = 'Some other rule'; - - const ruleNdjson = transformDataToNdjson([result1, result2]); - const ruleStrings = ruleNdjson.split('\n'); - const reParsed1 = JSON.parse(ruleStrings[0]); - const reParsed2 = JSON.parse(ruleStrings[1]); - expect(reParsed1).toEqual(result1); - expect(reParsed2).toEqual(result2); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index 42f1b467ed4c2..eb5abaee8cd3b 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -48,12 +48,3 @@ export const createLimitStream = (limit: number): Transform => { }, }); }; - -export const transformDataToNdjson = (data: unknown[]): string => { - if (data.length !== 0) { - const dataString = data.map((rule) => JSON.stringify(rule)).join('\n'); - return `${dataString}\n`; - } else { - return ''; - } -}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts new file mode 100644 index 0000000000000..d35d34fde5bcc --- /dev/null +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/export_exception_list.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { EXCEPTION_LIST_URL, EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + removeExceptionListServerGeneratedProperties, + removeExceptionListItemServerGeneratedProperties, + binaryToString, + deleteAllExceptions, +} from '../../utils'; +import { getCreateExceptionListMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('export_exception_list_route', () => { + describe('exporting exception lists', () => { + afterEach(async () => { + await deleteAllExceptions(es); + }); + + it('should set the response content types to be expected', async () => { + // create an exception list + const { body } = await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await supertest + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single` + ) + .set('kbn-xsrf', 'true') + .expect('Content-Disposition', `attachment; filename="${body.list_id}"`) + .expect(200); + }); + + it('should return 404 if given ids that do not exist', async () => { + // create an exception list + await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + const { body: exportBody } = await supertest + .post( + `${EXCEPTION_LIST_URL}/_export?id=not_exist&list_id=not_exist&namespace_type=single` + ) + .set('kbn-xsrf', 'true') + .expect(400); + + expect(exportBody).to.eql({ + message: 'exception list with list_id: not_exist does not exist', + status_code: 400, + }); + }); + + it('should export a single list with a list id', async () => { + const { body } = await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + const { body: itemBody } = await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + const { body: exportResult } = await supertest + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single` + ) + .set('kbn-xsrf', 'true') + .expect(200) + .parse(binaryToString); + + const exportedItemsToArray = exportResult.toString().split('\n'); + const list = JSON.parse(exportedItemsToArray[0]); + const item = JSON.parse(exportedItemsToArray[1]); + + expect(removeExceptionListServerGeneratedProperties(list)).to.eql( + removeExceptionListServerGeneratedProperties(body) + ); + expect(removeExceptionListItemServerGeneratedProperties(item)).to.eql( + removeExceptionListItemServerGeneratedProperties(itemBody) + ); + }); + + it('should export two list items with a list id', async () => { + const { body } = await supertest + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + const secondExceptionListItem: CreateExceptionListItemSchema = { + ...getCreateExceptionListItemMinimalSchemaMock(), + item_id: 'some-list-item-id-2', + }; + await supertest + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(secondExceptionListItem) + .expect(200); + + const { body: exportResult } = await supertest + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single` + ) + .set('kbn-xsrf', 'true') + .expect(200) + .parse(binaryToString); + + const bodyString = exportResult.toString(); + expect(bodyString.includes('some-list-item-id-2')).to.be(true); + expect(bodyString.includes('some-list-item-id')).to.be(true); + }); + }); + }); +}; diff --git a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts index 89a1183da6790..afb6057dedfff 100644 --- a/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/lists_api_integration/security_and_spaces/tests/index.ts @@ -24,6 +24,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./find_list_items')); loadTestFile(require.resolve('./import_list_items')); loadTestFile(require.resolve('./export_list_items')); + loadTestFile(require.resolve('./export_exception_list')); loadTestFile(require.resolve('./create_exception_lists')); loadTestFile(require.resolve('./create_exception_list_items')); loadTestFile(require.resolve('./read_exception_lists')); From 95e8595a1287ee9148846f1f9e4707b703c2d65d Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Wed, 13 Oct 2021 21:49:07 -0800 Subject: [PATCH 06/31] [Detection Rules] Add 7.16 rules (#114939) --- ...ion_email_powershell_exchange_mailbox.json | 11 +- .../collection_winrar_encryption.json | 11 +- ...ommand_and_control_common_webservices.json | 29 ++- ..._control_dns_directly_to_the_internet.json | 4 +- ...nd_and_control_dns_tunneling_nslookup.json | 11 +- ...download_rar_powershell_from_internet.json | 4 +- .../command_and_control_iexplore_via_com.json | 24 ++- ...ntrol_port_forwarding_added_registry.json} | 23 +-- ...command_and_control_rdp_tunnel_plink.json} | 18 +- ..._and_control_remote_file_copy_scripts.json | 4 +- ...d_control_teamviewer_remote_file_copy.json | 7 +- .../credential_access_cmdline_dump_tool.json | 16 +- ...ess_copy_ntds_sam_volshadowcp_cmdline.json | 11 +- ...cess_domain_backup_dpapi_private_keys.json | 7 +- ...credential_access_dump_registry_hives.json | 16 +- ..._access_kerberoasting_unusual_process.json | 4 +- .../credential_access_kerberosdump_kcc.json | 14 +- ...ial_access_lsass_memdump_file_created.json | 11 +- ..._potential_lsa_memdump_via_mirrordump.json | 55 ++++++ ...redential_access_saved_creds_vaultcmd.json | 14 +- ...e_evasion_clearing_windows_event_logs.json | 11 +- ...vasion_clearing_windows_security_logs.json | 14 +- ...ion_defender_exclusion_via_powershell.json | 4 +- ...ble_windows_firewall_rules_with_netsh.json | 8 +- ...efense_evasion_disabling_windows_logs.json | 11 +- ...vasion_dotnet_compiler_parent_process.json | 15 +- ...n_elasticache_security_group_creation.json | 61 ++++++ ...he_security_group_modified_or_deleted.json | 61 ++++++ ...evasion_enable_inbound_rdp_with_netsh.json | 8 +- ...n_enable_network_discovery_with_netsh.json | 8 +- ...ecution_control_panel_suspicious_args.json | 56 ++++++ ...ecution_msbuild_started_by_office_app.json | 11 +- ...n_execution_msbuild_started_by_script.json | 11 +- ...ion_msbuild_started_by_system_process.json | 11 +- ...ion_execution_msbuild_started_renamed.json | 11 +- ...sion_execution_windefend_unusual_path.json | 7 +- ..._evasion_file_creation_mult_extension.json | 24 ++- ...on_frontdoor_firewall_policy_deletion.json | 60 ++++++ ...sion_hide_encoded_executable_registry.json | 7 +- ...ense_evasion_iis_httplogging_disabled.json | 15 +- .../defense_evasion_installutil_beacon.json | 4 +- ...e_evasion_masquerading_renamed_autoit.json | 11 +- ...vasion_masquerading_trusted_directory.json | 11 +- ...defense_evasion_masquerading_werfault.json | 4 +- ...on_msbuild_making_network_connections.json | 11 +- ...cess_termination_followed_by_deletion.json | 11 +- .../defense_evasion_unusual_dir_ads.json | 11 +- .../defense_evasion_via_filter_manager.json | 15 +- ...on_whitespace_padding_in_command_line.json | 4 +- .../discovery_adfind_command_activity.json | 9 +- .../discovery_admin_recon.json | 11 +- .../discovery_net_command_system_account.json | 8 +- .../discovery_security_software_wmic.json | 11 +- ...y_virtual_machine_fingerprinting_grep.json | 52 ++++++ .../execution_enumeration_via_wmiprvse.json | 27 ++- ...le_program_connecting_to_the_internet.json | 17 +- ...tion_scheduled_task_powershell_source.json | 11 +- .../execution_via_compiled_html_file.json | 17 +- .../exfiltration_rds_snapshot_export.json | 5 +- .../impact_backup_file_deletion.json | 52 ++++++ ...eleting_backup_catalogs_with_wbadmin.json} | 23 +-- ...oft_365_potential_ransomware_activity.json | 54 ++++++ ...t_365_unusual_volume_of_file_deletion.json | 54 ++++++ ...> impact_modification_of_boot_config.json} | 23 +-- ...mpact_stop_process_service_threshold.json} | 25 +-- ...opy_deletion_or_resized_via_vssadmin.json} | 8 +- ...e_shadow_copy_deletion_via_powershell.json | 52 ++++++ ...volume_shadow_copy_deletion_via_wmic.json} | 23 +-- .../rules/prepackaged_rules/index.ts | 174 +++++++++++------- ...65_user_restricted_from_sending_email.json | 54 ++++++ ...ta_user_attempted_unauthorized_access.json | 74 ++++++++ ...ss_suspicious_ms_office_child_process.json | 4 +- ...ential_access_kerberos_bifrostconsole.json | 11 +- .../lateral_movement_dcom_hta.json | 35 +++- .../lateral_movement_dcom_mmc20.json | 13 +- ...t_dcom_shellwindow_shellbrowserwindow.json | 13 +- ...vement_direct_outbound_smb_connection.json | 15 +- .../lateral_movement_dns_server_overflow.json | 4 +- ...movement_executable_tool_transfer_smb.json | 4 +- ...vement_incoming_winrm_shell_execution.json | 4 +- .../lateral_movement_incoming_wmi.json | 4 +- ...l_movement_powershell_remoting_target.json | 4 +- ...lateral_movement_rdp_enabled_registry.json | 11 +- .../lateral_movement_rdp_sharprdp_target.json | 13 +- .../lateral_movement_remote_services.json | 4 +- ...ateral_movement_scheduled_task_target.json | 15 +- .../ml_auth_rare_user_logon.json | 4 +- ...pike_in_logon_events_from_a_source_ip.json | 4 +- .../ml_cloudtrail_error_message_spike.json | 4 +- .../ml_cloudtrail_rare_error_code.json | 4 +- .../ml_cloudtrail_rare_method_by_city.json | 4 +- .../ml_cloudtrail_rare_method_by_country.json | 4 +- .../ml_cloudtrail_rare_method_by_user.json | 4 +- .../ml_rare_process_by_host_windows.json | 4 +- ..._group_configuration_change_detection.json | 19 +- ...evasion_hidden_local_account_creation.json | 11 +- ...egistry_startup_shell_folder_modified.json | 4 +- ...e_suspicious_mailbox_right_delegation.json | 57 ++++++ ...sistence_gpo_schtask_service_creation.json | 11 +- ...sistence_local_scheduled_job_creation.json | 11 +- ...stence_local_scheduled_task_scripting.json | 11 +- ...l_exch_mailbox_activesync_add_device.json} | 28 +-- .../persistence_rds_instance_creation.json | 5 +- .../persistence_registry_uncommon.json | 17 +- ...tence_route_table_modified_or_deleted.json | 55 ++++++ ...saver_engine_unexpected_child_process.json | 50 +++++ ...e_screensaver_plist_file_modification.json | 50 +++++ ...nce_suspicious_scheduled_task_runtime.json | 11 +- ..._account_added_to_privileged_group_ad.json | 15 +- .../persistence_user_account_creation.json | 11 +- ...emetrycontroller_scheduledtask_hijack.json | 11 +- ...nt_instrumentation_event_subscription.json | 11 +- .../persistence_webshell_detection.json | 4 +- ...ion_new_or_modified_federation_domain.json | 61 ++++++ ..._escalation_sts_getsessiontoken_abuse.json | 74 ++++++++ ...tion_unusual_parentchild_relationship.json | 4 +- .../threat_intel_module_match.json | 6 +- 117 files changed, 1920 insertions(+), 377 deletions(-) rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{defense_evasion_port_forwarding_added_registry.json => command_and_control_port_forwarding_added_registry.json} (66%) rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{lateral_movement_rdp_tunnel_plink.json => command_and_control_rdp_tunnel_plink.json} (69%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{defense_evasion_deleting_backup_catalogs_with_wbadmin.json => impact_deleting_backup_catalogs_with_wbadmin.json} (66%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{defense_evasion_modification_of_boot_config.json => impact_modification_of_boot_config.json} (69%) rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{defense_evasion_stop_process_service_threshold.json => impact_stop_process_service_threshold.json} (64%) rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{impact_volume_shadow_copy_deletion_via_vssadmin.json => impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json} (70%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{defense_evasion_volume_shadow_copy_deletion_via_wmic.json => impact_volume_shadow_copy_deletion_via_wmic.json} (66%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{collection_persistence_powershell_exch_mailbox_activesync_add_device.json => persistence_powershell_exch_mailbox_activesync_add_device.json} (72%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_plist_file_modification.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json index 25ad15f1b0a51..6e2073bbb82b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json @@ -42,7 +42,14 @@ { "id": "T1114", "name": "Email Collection", - "reference": "https://attack.mitre.org/techniques/T1114/" + "reference": "https://attack.mitre.org/techniques/T1114/", + "subtechnique": [ + { + "id": "T1114.002", + "name": "Remote Email Collection", + "reference": "https://attack.mitre.org/techniques/T1114/002/" + } + ] }, { "id": "T1005", @@ -54,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json index 73c4300556a02..fa0ee2b18bb15 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json @@ -38,12 +38,19 @@ { "id": "T1560", "name": "Archive Collected Data", - "reference": "https://attack.mitre.org/techniques/T1560/" + "reference": "https://attack.mitre.org/techniques/T1560/", + "subtechnique": [ + { + "id": "T1560.001", + "name": "Archive via Utility", + "reference": "https://attack.mitre.org/techniques/T1560/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json index 0d80e78c556b9..b1774ab3dd052 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json @@ -38,9 +38,36 @@ "reference": "https://attack.mitre.org/techniques/T1102/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0010", + "name": "Exfiltration", + "reference": "https://attack.mitre.org/tactics/TA0010/" + }, + "technique": [ + { + "id": "T1567", + "name": "Exfiltration Over Web Service", + "reference": "https://attack.mitre.org/techniques/T1567/", + "subtechnique": [ + { + "id": "T1567.001", + "name": "Exfiltration to Code Repository", + "reference": "https://attack.mitre.org/techniques/T1567/001/" + }, + { + "id": "T1567.002", + "name": "Exfiltration to Cloud Storage", + "reference": "https://attack.mitre.org/techniques/T1567/002/" + } + ] + } + ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json index 8567b18670301..f57bd65b6d992 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_directly_to_the_internet.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "This rule detects when an internal network client sends DNS traffic directly to the Internet. This is atypical behavior for a managed network and can be indicative of malware, exfiltration, command and control, or simply misconfiguration. This DNS activity also impacts your organization's ability to provide enterprise monitoring and logging of DNS and it opens your network to a variety of abuses and malicious communications.", + "description": "This rule detects when an internal network client sends DNS traffic directly to the Internet. This is atypical behavior for a managed network and can be indicative of malware, exfiltration, command and control, or simply misconfiguration. This DNS activity also impacts your organization's ability to provide enterprise monitoring and logging of DNS, and it opens your network to a variety of abuses and malicious communications.", "false_positives": [ "Exclude DNS servers from this rule as this is expected behavior. Endpoints usually query local DNS servers defined in their DHCP scopes, but this may be overridden if a user configures their endpoint to use a remote DNS server. This is uncommon in managed enterprise networks because it could break intranet name resolution when split horizon DNS is utilized. Some consumer VPN services and browser plug-ins may send DNS traffic to remote Internet destinations. In that case, such devices or networks can be excluded from this rule when this is expected behavior." ], @@ -45,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 11 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json index 0920f336bab44..29c30f6bc0b49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json @@ -38,7 +38,14 @@ { "id": "T1071", "name": "Application Layer Protocol", - "reference": "https://attack.mitre.org/techniques/T1071/" + "reference": "https://attack.mitre.org/techniques/T1071/", + "subtechnique": [ + { + "id": "T1071.004", + "name": "DNS", + "reference": "https://attack.mitre.org/techniques/T1071/004/" + } + ] } ] } @@ -50,5 +57,5 @@ "value": 15 }, "type": "threshold", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json index 0bcbb0d2d031d..dcca38dd242d8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Detects a Roshal Archive (RAR) file or PowerShell script downloaded from the internet by an internal host. Gaining initial access to a system and then downloading encoded or encrypted tools to move laterally is a common practice for adversaries as a way to protect their more valuable tools and TTPs (tactics, techniques, and procedures). This may be atypical behavior for a managed network and can be indicative of malware, exfiltration, or command and control.", + "description": "Detects a Roshal Archive (RAR) file or PowerShell script downloaded from the internet by an internal host. Gaining initial access to a system and then downloading encoded or encrypted tools to move laterally is a common practice for adversaries as a way to protect their more valuable tools and tactics, techniques, and procedures (TTPs). This may be atypical behavior for a managed network and can be indicative of malware, exfiltration, or command and control.", "false_positives": [ "Downloading RAR or PowerShell files from the Internet may be expected for certain systems. This rule should be tailored to either exclude systems as sources or destinations in which this behavior is expected." ], @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json index 2cfbbc1c5e101..d0039ab4f02d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json @@ -41,8 +41,30 @@ "reference": "https://attack.mitre.org/techniques/T1071/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1559", + "name": "Inter-Process Communication", + "reference": "https://attack.mitre.org/techniques/T1559/", + "subtechnique": [ + { + "id": "T1559.001", + "name": "Component Object Model", + "reference": "https://attack.mitre.org/techniques/T1559/001/" + } + ] + } + ] } ], "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json similarity index 66% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json index cb5c8e87dcae8..65612e6c28f20 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_port_forwarding_added_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json @@ -24,33 +24,26 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Command and Control" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" + "id": "TA0011", + "name": "Command and Control", + "reference": "https://attack.mitre.org/tactics/TA0011/" }, "technique": [ { - "id": "T1562", - "name": "Impair Defenses", - "reference": "https://attack.mitre.org/techniques/T1562/", - "subtechnique": [ - { - "id": "T1562.001", - "name": "Disable or Modify Tools", - "reference": "https://attack.mitre.org/techniques/T1562/001/" - } - ] + "id": "T1572", + "name": "Protocol Tunneling", + "reference": "https://attack.mitre.org/techniques/T1572/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json index dd6bdfa0c37d6..3c89ff7c9ff9a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_tunnel_plink.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies potential use of an SSH utility to establish RDP over a reverse SSH Tunnel. This could be indicative of adversary lateral movement to interactively access restricted networks.", + "description": "Identifies potential use of an SSH utility to establish RDP over a reverse SSH Tunnel. This can be used by attackers to enable routing of network packets that would otherwise not reach their intended destination.", "from": "now-9m", "index": [ "logs-endpoint.events.*", @@ -24,26 +24,26 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Command and Control" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0008", - "name": "Lateral Movement", - "reference": "https://attack.mitre.org/tactics/TA0008/" + "id": "TA0011", + "name": "Command and Control", + "reference": "https://attack.mitre.org/tactics/TA0011/" }, "technique": [ { - "id": "T1021", - "name": "Remote Services", - "reference": "https://attack.mitre.org/techniques/T1021/" + "id": "T1572", + "name": "Protocol Tunneling", + "reference": "https://attack.mitre.org/techniques/T1572/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json index 428b08891c15a..eed29634daeef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via Script Interpreter", - "query": "sequence by host.id, process.entity_id\n [network where process.name : (\"wscript.exe\", \"cscript.exe\") and network.protocol != \"dns\" and\n network.direction == \"outgoing\" and network.type == \"ipv4\" and destination.ip != \"127.0.0.1\"\n ]\n [file where event.type == \"creation\" and file.extension : (\"exe\", \"dll\")]\n", + "query": "sequence by host.id, process.entity_id\n [network where process.name : (\"wscript.exe\", \"cscript.exe\") and network.protocol != \"dns\" and\n network.direction : (\"outgoing\", \"egress\") and network.type == \"ipv4\" and destination.ip != \"127.0.0.1\"\n ]\n [file where event.type == \"creation\" and file.extension : (\"exe\", \"dll\")]\n", "risk_score": 47, "rule_id": "1d276579-3380-4095-ad38-e596a01bc64f", "severity": "medium", @@ -41,5 +41,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json index 08d4df2556f6a..a1f0f061a69bc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json @@ -39,11 +39,16 @@ "id": "T1105", "name": "Ingress Tool Transfer", "reference": "https://attack.mitre.org/techniques/T1105/" + }, + { + "id": "T1219", + "name": "Remote Access Software", + "reference": "https://attack.mitre.org/techniques/T1219/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json index 32c271f736e4a..9671f3c4edf2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json @@ -38,12 +38,24 @@ { "id": "T1003", "name": "OS Credential Dumping", - "reference": "https://attack.mitre.org/techniques/T1003/" + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.001", + "name": "LSASS Memory", + "reference": "https://attack.mitre.org/techniques/T1003/001/" + }, + { + "id": "T1003.003", + "name": "NTDS", + "reference": "https://attack.mitre.org/techniques/T1003/003/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json index 91613078c6167..0aeba88224138 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json @@ -41,12 +41,19 @@ { "id": "T1003", "name": "OS Credential Dumping", - "reference": "https://attack.mitre.org/techniques/T1003/" + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.002", + "name": "Security Account Manager", + "reference": "https://attack.mitre.org/techniques/T1003/002/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json index c031fcbf464b1..43ea1078d1583 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json @@ -48,11 +48,16 @@ "reference": "https://attack.mitre.org/techniques/T1552/004/" } ] + }, + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json index c3868162cc839..10c6996fa56aa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json @@ -38,12 +38,24 @@ { "id": "T1003", "name": "OS Credential Dumping", - "reference": "https://attack.mitre.org/techniques/T1003/" + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.002", + "name": "Security Account Manager", + "reference": "https://attack.mitre.org/techniques/T1003/002/" + }, + { + "id": "T1003.004", + "name": "LSA Secrets", + "reference": "https://attack.mitre.org/techniques/T1003/004/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json index b05ddd7bcc8a2..8fc7cd7b379b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Kerberos Traffic from Unusual Process", - "query": "network where event.type == \"start\" and network.direction == \"outgoing\" and\n destination.port == 88 and source.port >= 49152 and\n process.executable != \"C:\\\\Windows\\\\System32\\\\lsass.exe\" and destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert False Positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", + "query": "network where event.type == \"start\" and network.direction : (\"outgoing\", \"egress\") and\n destination.port == 88 and source.port >= 49152 and\n process.executable != \"C:\\\\Windows\\\\System32\\\\lsass.exe\" and destination.address !=\"127.0.0.1\" and destination.address !=\"::1\" and\n /* insert False Positives here */\n not process.name in (\"swi_fc.exe\", \"fsIPcam.exe\", \"IPCamera.exe\", \"MicrosoftEdgeCP.exe\", \"MicrosoftEdge.exe\", \"iexplore.exe\", \"chrome.exe\", \"msedge.exe\", \"opera.exe\", \"firefox.exe\")\n", "risk_score": 47, "rule_id": "897dc6b5-b39f-432a-8d75-d3730d50c782", "severity": "medium", @@ -45,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json index de5a9d80ed3df..3338895f30feb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json @@ -39,11 +39,23 @@ "id": "T1003", "name": "OS Credential Dumping", "reference": "https://attack.mitre.org/techniques/T1003/" + }, + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/", + "subtechnique": [ + { + "id": "T1558.003", + "name": "Kerberoasting", + "reference": "https://attack.mitre.org/techniques/T1558/003/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json index 36b614c628b19..d083fb322e895 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json @@ -39,12 +39,19 @@ { "id": "T1003", "name": "OS Credential Dumping", - "reference": "https://attack.mitre.org/techniques/T1003/" + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.001", + "name": "LSASS Memory", + "reference": "https://attack.mitre.org/techniques/T1003/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json new file mode 100644 index 0000000000000..1024d7f3461f5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies suspicious access to an LSASS handle via DuplicateHandle from an unknown call trace module. This may indicate an attempt to bypass the NtOpenProcess API to evade detection and dump Lsass memory for credential access.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Credential Access via DuplicateHandle in LSASS", + "query": "process where event.code == \"10\" and \n\n /* LSASS requesting DuplicateHandle access right to another process */\n process.name : \"lsass.exe\" and winlog.event_data.GrantedAccess == \"0x40\" and\n\n /* call is coming from an unknown executable region */\n winlog.event_data.CallTrace : \"*UNKNOWN*\"\n", + "references": [ + "https://github.com/CCob/MirrorDump" + ], + "risk_score": 47, + "rule_id": "02a4576a-7480-4284-9327-548a806b5e48", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/", + "subtechnique": [ + { + "id": "T1003.001", + "name": "LSASS Memory", + "reference": "https://attack.mitre.org/techniques/T1003/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json index bfb4e44d39b0d..c6db4426ac8c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json @@ -40,11 +40,23 @@ "id": "T1003", "name": "OS Credential Dumping", "reference": "https://attack.mitre.org/techniques/T1003/" + }, + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/", + "subtechnique": [ + { + "id": "T1555.004", + "name": "Windows Credential Manager", + "reference": "https://attack.mitre.org/techniques/T1555/004/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json index 79e059d68a52a..2759055b0fe5b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json @@ -35,12 +35,19 @@ { "id": "T1070", "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/" + "reference": "https://attack.mitre.org/techniques/T1070/", + "subtechnique": [ + { + "id": "T1070.001", + "name": "Clear Windows Event Logs", + "reference": "https://attack.mitre.org/techniques/T1070/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json index d04c2b2a38915..eedca883e371c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json @@ -7,7 +7,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "logs-system.*" ], "language": "kuery", "license": "Elastic License v2", @@ -35,12 +36,19 @@ { "id": "T1070", "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/" + "reference": "https://attack.mitre.org/techniques/T1070/", + "subtechnique": [ + { + "id": "T1070.001", + "name": "Clear Windows Event Logs", + "reference": "https://attack.mitre.org/techniques/T1070/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json index 000384eac660e..716040d337c10 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Defender Exclusions Added via PowerShell", - "note": "## Triage and analysis\n\nDetections should be investigated to identify if the activity corresponds to legitimate activity used to put in exceptions for Windows Defender. As this rule detects post-exploitation process activity, investigations into this should be prioritized.", + "note": "## Triage and analysis\n\n### Investigating Windows Defender Exclusions\n\nMicrosoft Windows Defender is an anti-virus product built-in within Microsoft Windows. Since this software product is\nused to prevent and stop malware, it's important to monitor what specific exclusions are made to the product's configuration\nsettings. These can often be signs of an adversary or malware trying to bypass Windows Defender's capabilities. One of the more\nnotable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/) was observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defense to avoid detection.\n\n#### Possible investigation steps:\n- With this specific rule, it's completely possible to trigger detections on network administrative activity or benign users\nusing scripting and PowerShell to configure the different exclusions for Windows Defender. Therefore, it's important to\nidentify the source of the activity first and determine if there is any mal-intent behind the events.\n- The actual exclusion such as the process, the file or directory should be reviewed in order to determine the original\nintent behind the exclusion. Is the excluded file or process malicious in nature or is it related to software that needs\nto be legitimately whitelisted from Windows Defender?\n\n### False Positive Analysis\n- This rule has a higher chance to produce false positives based on the nature around configuring exclusions by possibly\na network administrator. In order to validate the activity further, review the specific exclusion made and determine based\non the exclusion of the original intent behind the exclusion. There are often many legitimate reasons why exclusions are made\nwith Windows Defender so it's important to gain context around the exclusion.\n\n### Related Rules\n- Windows Defender Disabled via Registry Modification\n- Disabling Windows Defender Security Settings via PowerShell\n\n### Response and Remediation\n- Since this is related to post-exploitation activity, immediate response should be taken to review, investigate and\npotentially isolate further activity\n- If further analysis showed malicious intent was behind the Defender exclusions, administrators should remove\nthe exclusion and ensure antimalware capability has not been disabled or deleted\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review\n", "query": "process where event.type == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\") or process.pe.original_file_name : (\"powershell.exe\", \"pwsh.exe\")) and\n process.args : (\"*Add-MpPreference*-Exclusion*\", \"*Set-MpPreference*-Exclusion*\")\n", "references": [ "https://www.bitdefender.com/files/News/CaseStudies/study/400/Bitdefender-PR-Whitepaper-MosaicLoader-creat5540-en-EN.pdf" @@ -80,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json index 00f18df34f864..2e18f3ba62786 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json @@ -38,9 +38,9 @@ "reference": "https://attack.mitre.org/techniques/T1562/", "subtechnique": [ { - "id": "T1562.001", - "name": "Disable or Modify Tools", - "reference": "https://attack.mitre.org/techniques/T1562/001/" + "id": "T1562.004", + "name": "Disable or Modify System Firewall", + "reference": "https://attack.mitre.org/techniques/T1562/004/" } ] } @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json index d2612101a3e4c..256d1c7d9c135 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json @@ -40,12 +40,19 @@ { "id": "T1070", "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/" + "reference": "https://attack.mitre.org/techniques/T1070/", + "subtechnique": [ + { + "id": "T1070.001", + "name": "Clear Windows Event Logs", + "reference": "https://attack.mitre.org/techniques/T1070/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json index 4588a8ab28657..e8edb8fba6472 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json @@ -33,14 +33,21 @@ }, "technique": [ { - "id": "T1055", - "name": "Process Injection", - "reference": "https://attack.mitre.org/techniques/T1055/" + "id": "T1027", + "name": "Obfuscated Files or Information", + "reference": "https://attack.mitre.org/techniques/T1027/", + "subtechnique": [ + { + "id": "T1027.004", + "name": "Compile After Delivery", + "reference": "https://attack.mitre.org/techniques/T1027/004/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json new file mode 100644 index 0000000000000..5685ac76b3ef9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies when an ElastiCache security group has been created.", + "false_positives": [ + "A ElastiCache security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + ], + "from": "now-60m", + "index": [ + "filebeat-*", + "logs-aws*" + ], + "interval": "10m", + "language": "kuery", + "license": "Elastic License v2", + "name": "AWS ElastiCache Security Group Created", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:aws.cloudtrail and event.provider:elasticache.amazonaws.com and event.action:\"Create Cache Security Group\" and \nevent.outcome:success\n", + "references": [ + "https://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_CreateCacheSecurityGroup.html" + ], + "risk_score": 21, + "rule_id": "7b3da11a-60a2-412e-8aa7-011e1eb9ed47", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Monitoring" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.007", + "name": "Disable or Modify Cloud Firewall", + "reference": "https://attack.mitre.org/techniques/T1562/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json new file mode 100644 index 0000000000000..83b58c0c046e0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies when an ElastiCache security group has been modified or deleted.", + "false_positives": [ + "A ElastiCache security group deletion may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security Group deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + ], + "from": "now-60m", + "index": [ + "filebeat-*", + "logs-aws*" + ], + "interval": "10m", + "language": "kuery", + "license": "Elastic License v2", + "name": "AWS ElastiCache Security Group Modified or Deleted", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:aws.cloudtrail and event.provider:elasticache.amazonaws.com and event.action:(\"Delete Cache Security Group\" or \n\"Authorize Cache Security Group Ingress\" or \"Revoke Cache Security Group Ingress\" or \"AuthorizeCacheSecurityGroupEgress\" or \n\"RevokeCacheSecurityGroupEgress\") and event.outcome:success\n", + "references": [ + "https://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/Welcome.html" + ], + "risk_score": 21, + "rule_id": "1ba5160d-f5a2-4624-b0ff-6a1dc55d2516", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Monitoring" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.007", + "name": "Disable or Modify Cloud Firewall", + "reference": "https://attack.mitre.org/techniques/T1562/007/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json index 93454122d1160..e6b53af71433a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json @@ -38,9 +38,9 @@ "reference": "https://attack.mitre.org/techniques/T1562/", "subtechnique": [ { - "id": "T1562.001", - "name": "Disable or Modify Tools", - "reference": "https://attack.mitre.org/techniques/T1562/001/" + "id": "T1562.004", + "name": "Disable or Modify System Firewall", + "reference": "https://attack.mitre.org/techniques/T1562/004/" } ] } @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json index 5fcbec498a177..bf688fd74ce14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json @@ -41,9 +41,9 @@ "reference": "https://attack.mitre.org/techniques/T1562/", "subtechnique": [ { - "id": "T1562.001", - "name": "Disable or Modify Tools", - "reference": "https://attack.mitre.org/techniques/T1562/001/" + "id": "T1562.004", + "name": "Disable or Modify System Firewall", + "reference": "https://attack.mitre.org/techniques/T1562/004/" } ] } @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json new file mode 100644 index 0000000000000..787e61cfe25c4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json @@ -0,0 +1,56 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies unusual instances of Control Panel with suspicious keywords or paths in the process command line value. Adversaries may abuse Control.exe to proxy execution of malicious code.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "winlogbeat-*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Control Panel Process with Unusual Arguments", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.executable : (\"?:\\\\Windows\\\\SysWOW64\\\\control.exe\", \"?:\\\\Windows\\\\System32\\\\control.exe\") and\n process.command_line :\n (\"*.jpg*\",\n \"*.png*\",\n \"*.gif*\",\n \"*.bmp*\",\n \"*.jpeg*\",\n \"*.TIFF*\",\n \"*.inf*\",\n \"*.dat*\",\n \"*.cpl:*/*\",\n \"*../../..*\",\n \"*/AppData/Local/*\",\n \"*:\\\\Users\\\\Public\\\\*\",\n \"*\\\\AppData\\\\Local\\\\*\")\n", + "references": [ + "https://www.joesandbox.com/analysis/476188/1/html" + ], + "risk_score": 73, + "rule_id": "416697ae-e468-4093-a93d-59661fa619ec", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1218", + "name": "Signed Binary Proxy Execution", + "reference": "https://attack.mitre.org/techniques/T1218/", + "subtechnique": [ + { + "id": "T1218.002", + "name": "Control Panel", + "reference": "https://attack.mitre.org/techniques/T1218/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json index d56c90552d457..0ad45f03a0499 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json @@ -41,7 +41,14 @@ { "id": "T1127", "name": "Trusted Developer Utilities Proxy Execution", - "reference": "https://attack.mitre.org/techniques/T1127/" + "reference": "https://attack.mitre.org/techniques/T1127/", + "subtechnique": [ + { + "id": "T1127.001", + "name": "MSBuild", + "reference": "https://attack.mitre.org/techniques/T1127/001/" + } + ] } ] }, @@ -57,5 +64,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json index 3b640d8757b51..60b2a8f50c3f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json @@ -38,7 +38,14 @@ { "id": "T1127", "name": "Trusted Developer Utilities Proxy Execution", - "reference": "https://attack.mitre.org/techniques/T1127/" + "reference": "https://attack.mitre.org/techniques/T1127/", + "subtechnique": [ + { + "id": "T1127.001", + "name": "MSBuild", + "reference": "https://attack.mitre.org/techniques/T1127/001/" + } + ] } ] }, @@ -54,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json index 33094a88af313..fdee8ee548218 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json @@ -38,7 +38,14 @@ { "id": "T1127", "name": "Trusted Developer Utilities Proxy Execution", - "reference": "https://attack.mitre.org/techniques/T1127/" + "reference": "https://attack.mitre.org/techniques/T1127/", + "subtechnique": [ + { + "id": "T1127.001", + "name": "MSBuild", + "reference": "https://attack.mitre.org/techniques/T1127/001/" + } + ] } ] }, @@ -54,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json index 43051cb8b27c9..a22594083bedb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json @@ -38,12 +38,19 @@ { "id": "T1036", "name": "Masquerading", - "reference": "https://attack.mitre.org/techniques/T1036/" + "reference": "https://attack.mitre.org/techniques/T1036/", + "subtechnique": [ + { + "id": "T1036.003", + "name": "Rename System Utilities", + "reference": "https://attack.mitre.org/techniques/T1036/003/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json index 7812dee8235ca..826d55f3b1882 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json @@ -1,6 +1,7 @@ { "author": [ - "Elastic" + "Elastic", + "Dennis Perto" ], "description": "Identifies a Windows trusted program that is known to be vulnerable to DLL Search Order Hijacking starting after being renamed or from a non-standard path. This is uncommon behavior and may indicate an attempt to evade defenses via side-loading a malicious DLL within the memory space of one of those processes.", "false_positives": [ @@ -15,7 +16,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential DLL Side-Loading via Microsoft Antimalware Service Executable", - "query": "process where event.type == \"start\" and\n (process.pe.original_file_name == \"MsMpEng.exe\" and not process.name : \"MsMpEng.exe\") or\n (process.name : \"MsMpEng.exe\" and not\n process.executable : (\"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Program Files\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\Windows Defender\\\\*.exe\"))\n", + "query": "process where event.type == \"start\" and\n (process.pe.original_file_name == \"MsMpEng.exe\" and not process.name : \"MsMpEng.exe\") or\n (process.name : \"MsMpEng.exe\" and not\n process.executable : (\"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Program Files\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Program Files\\\\Microsoft Security Client\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\Microsoft Security Client\\\\*.exe\"))\n", "references": [ "https://news.sophos.com/en-us/2021/07/04/independence-day-revil-uses-supply-chain-exploit-to-attack-hundreds-of-businesses/" ], @@ -55,5 +56,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json index 24cbb1e41dad6..4cbfb8bbbce6c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json @@ -45,9 +45,31 @@ ] } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1204", + "name": "User Execution", + "reference": "https://attack.mitre.org/techniques/T1204/", + "subtechnique": [ + { + "id": "T1204.002", + "name": "Malicious File", + "reference": "https://attack.mitre.org/techniques/T1204/002/" + } + ] + } + ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json new file mode 100644 index 0000000000000..c443d45dde4f0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies the deletion of a Frontdoor Web Application Firewall (WAF) Policy in Azure. An adversary may delete a Frontdoor Web Application Firewall (WAF) Policy in an attempt to evade defenses and/or to eliminate barriers in carrying out their initiative.", + "false_positives": [ + "Azure Front Web Application Firewall (WAF) Policy deletions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Azure Front Web Application Firewall (WAF) Policy deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + ], + "from": "now-25m", + "index": [ + "filebeat-*", + "logs-azure*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Azure Frontdoor Web Application Firewall (WAF) Policy Deleted", + "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.NETWORK/FRONTDOORWEBAPPLICATIONFIREWALLPOLICIES/DELETE\" and event.outcome:(Success or success)\n", + "references": [ + "https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations#networking" + ], + "risk_score": 21, + "rule_id": "09d028a5-dcde-409f-8ae0-557cef1b7082", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "Azure", + "Continuous Monitoring", + "SecOps", + "Network Security" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json index 006f95054d047..c40bbf236d668 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json @@ -36,11 +36,16 @@ "id": "T1140", "name": "Deobfuscate/Decode Files or Information", "reference": "https://attack.mitre.org/techniques/T1140/" + }, + { + "id": "T1112", + "name": "Modify Registry", + "reference": "https://attack.mitre.org/techniques/T1112/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json index 16de1c9c21f97..da12646d40226 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json @@ -34,14 +34,21 @@ }, "technique": [ { - "id": "T1070", - "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/" + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.002", + "name": "Disable Windows Event Logging", + "reference": "https://attack.mitre.org/techniques/T1562/002/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json index 4917cffd64ccb..72ef939fd2c1c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "InstallUtil Process Making Network Connections", - "query": "/* the benefit of doing this as an eql sequence vs kql is this will limit to alerting only on the first network connection */\n\nsequence by process.entity_id\n [process where event.type in (\"start\", \"process_started\") and process.name : \"installutil.exe\"]\n [network where process.name : \"installutil.exe\" and network.direction == \"outgoing\"]\n", + "query": "/* the benefit of doing this as an eql sequence vs kql is this will limit to alerting only on the first network connection */\n\nsequence by process.entity_id\n [process where event.type in (\"start\", \"process_started\") and process.name : \"installutil.exe\"]\n [network where process.name : \"installutil.exe\" and network.direction : (\"outgoing\", \"egress\")]\n", "risk_score": 21, "rule_id": "a13167f1-eec2-4015-9631-1fee60406dcf", "severity": "medium", @@ -48,5 +48,5 @@ } ], "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json index bd0a3ac9f918d..5c855207dda7d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json @@ -35,12 +35,19 @@ { "id": "T1036", "name": "Masquerading", - "reference": "https://attack.mitre.org/techniques/T1036/" + "reference": "https://attack.mitre.org/techniques/T1036/", + "subtechnique": [ + { + "id": "T1036.003", + "name": "Rename System Utilities", + "reference": "https://attack.mitre.org/techniques/T1036/003/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json index b0d11121c1a15..7ac21a70100c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json @@ -35,12 +35,19 @@ { "id": "T1036", "name": "Masquerading", - "reference": "https://attack.mitre.org/techniques/T1036/" + "reference": "https://attack.mitre.org/techniques/T1036/", + "subtechnique": [ + { + "id": "T1036.005", + "name": "Match Legitimate Name or Location", + "reference": "https://attack.mitre.org/techniques/T1036/005/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json index 2733bf992838e..a08e3040c6c95 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Windows Error Manager Masquerading", - "query": "sequence by host.id, process.entity_id with maxspan = 5s\n [process where event.type:\"start\" and process.name : (\"wermgr.exe\", \"WerFault.exe\") and process.args_count == 1]\n [network where process.name : (\"wermgr.exe\", \"WerFault.exe\") and network.protocol != \"dns\" and\n network.direction == \"outgoing\" and destination.ip !=\"::1\" and destination.ip !=\"127.0.0.1\"\n ]\n", + "query": "sequence by host.id, process.entity_id with maxspan = 5s\n [process where event.type:\"start\" and process.name : (\"wermgr.exe\", \"WerFault.exe\") and process.args_count == 1]\n [network where process.name : (\"wermgr.exe\", \"WerFault.exe\") and network.protocol != \"dns\" and\n network.direction : (\"outgoing\", \"egress\") and destination.ip !=\"::1\" and destination.ip !=\"127.0.0.1\"\n ]\n", "references": [ "https://twitter.com/SBousseaden/status/1235533224337641473", "https://www.hexacorn.com/blog/2019/09/20/werfault-command-line-switches-v0-1/", @@ -49,5 +49,5 @@ } ], "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json index a2019165f93c6..6d0110c229c33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json @@ -35,11 +35,18 @@ { "id": "T1127", "name": "Trusted Developer Utilities Proxy Execution", - "reference": "https://attack.mitre.org/techniques/T1127/" + "reference": "https://attack.mitre.org/techniques/T1127/", + "subtechnique": [ + { + "id": "T1127.001", + "name": "MSBuild", + "reference": "https://attack.mitre.org/techniques/T1127/001/" + } + ] } ] } ], "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json index b7d65b2336001..85316f7836b89 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json @@ -33,11 +33,18 @@ { "id": "T1070", "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/" + "reference": "https://attack.mitre.org/techniques/T1070/", + "subtechnique": [ + { + "id": "T1070.004", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1070/004/" + } + ] } ] } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json index 196a3de9b9e6f..f926a1ba24faf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json @@ -35,12 +35,19 @@ { "id": "T1564", "name": "Hide Artifacts", - "reference": "https://attack.mitre.org/techniques/T1564/" + "reference": "https://attack.mitre.org/techniques/T1564/", + "subtechnique": [ + { + "id": "T1564.004", + "name": "NTFS File Attributes", + "reference": "https://attack.mitre.org/techniques/T1564/004/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json index 51d1789804548..c0d171739b76d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json @@ -33,14 +33,21 @@ }, "technique": [ { - "id": "T1222", - "name": "File and Directory Permissions Modification", - "reference": "https://attack.mitre.org/techniques/T1222/" + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "reference": "https://attack.mitre.org/techniques/T1562/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_whitespace_padding_in_command_line.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_whitespace_padding_in_command_line.json index fc9b480023c95..f022f0c27ff5e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_whitespace_padding_in_command_line.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_whitespace_padding_in_command_line.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Whitespace Padding in Process Command Line", - "note": "## Triage and analysis\n\n- Analyze the command line of the process in question for evidence of malicious code execution.\n- Review the ancestry and child processes spawned by the process in question for indicators of further malicious code execution.", + "note": "## Triage and analysis\n\n- Analyze the command line of the process in question for evidence of malicious code execution.\n- Review the ancestor and child processes spawned by the process in question for indicators of further malicious code execution.", "query": "process where event.type in (\"start\", \"process_started\") and\n process.command_line regex \".*[ ]{20,}.*\" or \n \n /* this will match on 3 or more separate occurrences of 5+ contiguous whitespace characters */\n process.command_line regex \".*(.*[ ]{5,}[^ ]*){3,}.*\"\n", "references": [ "https://twitter.com/JohnLaTwC/status/1419251082736201737" @@ -40,5 +40,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json index 97ba7da6c5f3b..9af3832303666 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "AdFind Command Activity", - "note": "## Triage and analysis\n\n`AdFind.exe` is a legitimate domain query tool. Rule alerts should be investigated to identify if the user has a role that would explain using this tool and that it is being run from an expected directory and endpoint. Leverage the exception workflow in the Kibana Security App or Elasticsearch API to tune this rule to your environment.", + "note": "## Triage and analysis\n\n### Investigating AdFind Command Activity\n\n[AdFind](http://www.joeware.net/freetools/tools/adfind/) is a freely available command-line tool used to retrieve information from\nActivity Directory (AD). Network discovery and enumeration tools like `AdFind` are useful to adversaries in the same ways\nthey are effective for network administrators. This tool provides quick ability to scope AD person/computer objects and\nunderstand subnets and domain information. There are many [examples](https://thedfirreport.com/category/adfind/)\nobserved where this tool has been adopted by ransomware and criminal groups and used in compromises.\n\n#### Possible investigation steps:\n- `AdFind` is a legitimate Active Directory enumeration tool used by network administrators, it's important to understand\nthe source of the activity. This could involve identifying the account using `AdFind` and determining based on the command-lines\nwhat information was retrieved, then further determining if these actions are in scope of that user's traditional responsibilities.\n- In multiple public references, `AdFind` is leveraged after initial access is achieved, review previous activity on impacted\nmachine looking for suspicious indicators such as previous anti-virus/EDR alerts, phishing emails received, or network traffic\nto suspicious infrastructure\n\n### False Positive Analysis\n- This rule has the high chance to produce false positives as it is a legitimate tool used by network administrators. One\noption could be whitelisting specific users or groups who use the tool as part of their daily responsibilities. This can\nbe done by leveraging the exception workflow in the Kibana Security App or Elasticsearch API to tune this rule to your environment\n- Malicious behavior with `AdFind` should be investigated as part of a step within an attack chain. It doesn't happen in\nisolation, so reviewing previous logs/activity from impacted machines could be very telling.\n\n### Related Rules\n- Windows Network Enumeration\n- Enumeration of Administrator Accounts\n- Enumeration Command Spawned via WMIPrvSE\n\n### Response and Remediation\n- Immediate response should be taken to validate activity, investigate and potentially isolate activity to prevent further\npost-compromise behavior\n- It's important to understand that `AdFind` is an Active Directory enumeration tool and can be used for malicious or legitimate\npurposes, so understanding the intent behind the activity will help determine the appropropriate response.\n", "query": "process where event.type in (\"start\", \"process_started\") and \n (process.name : \"AdFind.exe\" or process.pe.original_file_name == \"AdFind.exe\") and \n process.args : (\"objectcategory=computer\", \"(objectcategory=computer)\", \n \"objectcategory=person\", \"(objectcategory=person)\",\n \"objectcategory=subnet\", \"(objectcategory=subnet)\",\n \"objectcategory=group\", \"(objectcategory=group)\", \n \"objectcategory=organizationalunit\", \"(objectcategory=organizationalunit)\",\n \"objectcategory=attributeschema\", \"(objectcategory=attributeschema)\",\n \"domainlist\", \"dcmodes\", \"adinfo\", \"dclist\", \"computers_pwnotreqd\", \"trustdmp\")\n", "references": [ "http://www.joeware.net/freetools/tools/adfind/", @@ -69,11 +69,16 @@ "id": "T1482", "name": "Domain Trust Discovery", "reference": "https://attack.mitre.org/techniques/T1482/" + }, + { + "id": "T1018", + "name": "Remote System Discovery", + "reference": "https://attack.mitre.org/techniques/T1018/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json index 1a3ceebe7218f..d5026780fdf56 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json @@ -35,7 +35,14 @@ { "id": "T1069", "name": "Permission Groups Discovery", - "reference": "https://attack.mitre.org/techniques/T1069/" + "reference": "https://attack.mitre.org/techniques/T1069/", + "subtechnique": [ + { + "id": "T1069.002", + "name": "Domain Groups", + "reference": "https://attack.mitre.org/techniques/T1069/002/" + } + ] }, { "id": "T1087", @@ -47,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json index 87b32d14791bb..dc855f3ed9a57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json @@ -33,14 +33,14 @@ }, "technique": [ { - "id": "T1087", - "name": "Account Discovery", - "reference": "https://attack.mitre.org/techniques/T1087/" + "id": "T1033", + "name": "System Owner/User Discovery", + "reference": "https://attack.mitre.org/techniques/T1033/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json index d0f26c6e41756..92731ab40e78a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json @@ -35,12 +35,19 @@ { "id": "T1518", "name": "Software Discovery", - "reference": "https://attack.mitre.org/techniques/T1518/" + "reference": "https://attack.mitre.org/techniques/T1518/", + "subtechnique": [ + { + "id": "T1518.001", + "name": "Security Software Discovery", + "reference": "https://attack.mitre.org/techniques/T1518/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json new file mode 100644 index 0000000000000..e557e37db23d6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "An adversary may attempt to get detailed information about the operating system and hardware. This rule identifies common locations used to discover virtual machine hardware by a non-root user. This technique has been used by the Pupy RAT and other malware.", + "false_positives": [ + "Certain tools or automated software may enumerate hardware information. These tools can be exempted via user name or process arguments to eliminate potential noise." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Virtual Machine Fingerprinting via Grep", + "query": "process where event.type == \"start\" and\n process.name in (\"grep\", \"egrep\") and user.id != \"0\" and\n process.args : (\"parallels*\", \"vmware*\", \"virtualbox*\") and process.args : \"Manufacturer*\" and \n not process.parent.executable in (\"/Applications/Docker.app/Contents/MacOS/Docker\", \"/usr/libexec/kcare/virt-what\")\n", + "references": [ + "https://objective-see.com/blog/blog_0x4F.html" + ], + "risk_score": 47, + "rule_id": "c85eb82c-d2c8-485c-a36f-534f914b7663", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Linux", + "Threat Detection", + "Discovery" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1082", + "name": "System Information Discovery", + "reference": "https://attack.mitre.org/techniques/T1082/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json index 6a967d9644c47..441e01b4a1b12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json @@ -38,9 +38,34 @@ "reference": "https://attack.mitre.org/techniques/T1047/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1518", + "name": "Software Discovery", + "reference": "https://attack.mitre.org/techniques/T1518/" + }, + { + "id": "T1087", + "name": "Account Discovery", + "reference": "https://attack.mitre.org/techniques/T1087/" + }, + { + "id": "T1018", + "name": "Remote System Discovery", + "reference": "https://attack.mitre.org/techniques/T1018/" + } + ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json index abc41d9f6d5c3..094b87f33ada7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json @@ -34,7 +34,20 @@ "name": "Execution", "reference": "https://attack.mitre.org/tactics/TA0002/" }, - "technique": [] + "technique": [ + { + "id": "T1204", + "name": "User Execution", + "reference": "https://attack.mitre.org/techniques/T1204/", + "subtechnique": [ + { + "id": "T1204.002", + "name": "Malicious File", + "reference": "https://attack.mitre.org/techniques/T1204/002/" + } + ] + } + ] }, { "framework": "MITRE ATT&CK", @@ -60,5 +73,5 @@ } ], "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json index 24492343e98c0..3814b00321417 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json @@ -41,11 +41,18 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json index efc3884b417fb..73c796c4e206d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json @@ -34,7 +34,20 @@ "name": "Execution", "reference": "https://attack.mitre.org/tactics/TA0002/" }, - "technique": [] + "technique": [ + { + "id": "T1204", + "name": "User Execution", + "reference": "https://attack.mitre.org/techniques/T1204/", + "subtechnique": [ + { + "id": "T1204.002", + "name": "Malicious File", + "reference": "https://attack.mitre.org/techniques/T1204/002/" + } + ] + } + ] }, { "framework": "MITRE ATT&CK", @@ -61,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json index 430d97690b6f4..b59adc45b4236 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json @@ -1,6 +1,7 @@ { "author": [ - "Elastic" + "Elastic", + "Austin Songer" ], "description": "Identifies the export of an Amazon Relational Database Service (RDS) Aurora database snapshot.", "false_positives": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json new file mode 100644 index 0000000000000..93c4c287d12ce --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the deletion of backup files, saved using third-party software, by a process outside of the backup suite. Adversaries may delete Backup files to ensure that recovery from a Ransomware attack is less likely.", + "false_positives": [ + "Certain utilities that delete files for disk cleanup or Administrators manually removing backup files." + ], + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Third-party Backup Files Deleted via Unexpected Process", + "query": "file where event.type == \"deletion\" and\n (\n /* Veeam Related Backup Files */\n (file.extension : (\"VBK\", \"VIB\", \"VBM\") and\n not process.executable : (\"?:\\\\Windows\\\\Veeam\\\\Backup\\\\*\",\n \"?:\\\\Program Files\\\\Veeam\\\\Backup and Replication\\\\*\",\n \"?:\\\\Program Files (x86)\\\\Veeam\\\\Backup and Replication\\\\*\")) or\n\n /* Veritas Backup Exec Related Backup File */\n (file.extension : \"BKF\" and\n not process.executable : (\"?:\\\\Program Files\\\\Veritas\\\\Backup Exec\\\\*\",\n \"?:\\\\Program Files (x86)\\\\Veritas\\\\Backup Exec\\\\*\"))\n )\n", + "references": [ + "https://www.advintel.io/post/backup-removal-solutions-from-conti-ransomware-with-love" + ], + "risk_score": 47, + "rule_id": "11ea6bec-ebde-4d71-a8e9-784948f8e3e9", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Impact" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "technique": [ + { + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json similarity index 66% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json index 5d1233ebfcb78..0c0c2a71b8263 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json @@ -21,33 +21,26 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Impact" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" }, "technique": [ { - "id": "T1070", - "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/", - "subtechnique": [ - { - "id": "T1070.004", - "name": "File Deletion", - "reference": "https://attack.mitre.org/techniques/T1070/004/" - } - ] + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json new file mode 100644 index 0000000000000..52094400232b6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies when Microsoft Cloud App Security reported when a user uploads files to the cloud that might be infected with ransomware.", + "false_positives": [ + "If Cloud App Security identifies, for example, a high rate of file uploads or file deletion activities it may represent an adverse encryption process." + ], + "from": "now-30m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Microsoft 365 Potential ransomware activity", + "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", + "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.category:web and event.action:\"Potential ransomware activity\" and event.outcome:success\n", + "references": [ + "https://docs.microsoft.com/en-us/cloud-app-security/anomaly-detection-policy", + "https://docs.microsoft.com/en-us/cloud-app-security/policy-template-reference" + ], + "risk_score": 47, + "rule_id": "721999d0-7ab2-44bf-b328-6e63367b9b29", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Configuration Audit" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "technique": [ + { + "id": "T1486", + "name": "Data Encrypted for Impact", + "reference": "https://attack.mitre.org/techniques/T1486/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json new file mode 100644 index 0000000000000..c3a53310781df --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies that a user has deleted an unusually large volume of files as reported by Microsoft Cloud App Security.", + "false_positives": [ + "Users or System Administrator cleaning out folders." + ], + "from": "now-30m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Microsoft 365 Unusual Volume of File Deletion", + "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", + "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.category:web and event.action:\"Unusual volume of file deletion\" and event.outcome:success\n", + "references": [ + "https://docs.microsoft.com/en-us/cloud-app-security/anomaly-detection-policy", + "https://docs.microsoft.com/en-us/cloud-app-security/policy-template-reference" + ], + "risk_score": 47, + "rule_id": "b2951150-658f-4a60-832f-a00d1e6c6745", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Configuration Audit" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "technique": [ + { + "id": "T1485", + "name": "Data Destruction", + "reference": "https://attack.mitre.org/techniques/T1485/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json index 7c58d82ec1061..91f5959bee119 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modification_of_boot_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json @@ -21,33 +21,26 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Impact" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" }, "technique": [ { - "id": "T1070", - "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/", - "subtechnique": [ - { - "id": "T1070.004", - "name": "File Deletion", - "reference": "https://attack.mitre.org/techniques/T1070/004/" - } - ] + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json similarity index 64% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json index 86903058b62fe..ec361a8795538 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_stop_process_service_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "This rule identifies a high number (10) of process terminations (stop, delete, or suspend) from the same host within a short time period. This may indicate a defense evasion attempt.", + "description": "This rule identifies a high number (10) of process terminations (stop, delete, or suspend) from the same host within a short time period.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -21,28 +21,21 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Impact" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" }, "technique": [ { - "id": "T1562", - "name": "Impair Defenses", - "reference": "https://attack.mitre.org/techniques/T1562/", - "subtechnique": [ - { - "id": "T1562.001", - "name": "Disable or Modify Tools", - "reference": "https://attack.mitre.org/techniques/T1562/001/" - } - ] + "id": "T1489", + "name": "Service Stop", + "reference": "https://attack.mitre.org/techniques/T1489/" } ] } @@ -54,5 +47,5 @@ "value": 10 }, "type": "threshold", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json similarity index 70% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json index f0ac38e98441e..940229bf63751 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies use of vssadmin.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", + "description": "Identifies use of vssadmin.exe for shadow copy deletion or resizing on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,8 +11,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Volume Shadow Copy Deletion via VssAdmin", - "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"vssadmin.exe\" or process.pe.original_file_name == \"VSSADMIN.EXE\") and\n process.args : \"delete\" and process.args : \"shadows\"\n", + "name": "Volume Shadow Copy Deleted or Resized via VssAdmin", + "query": "process where event.type in (\"start\", \"process_started\") and event.action == \"start\" \n and (process.name : \"vssadmin.exe\" or process.pe.original_file_name == \"VSSADMIN.EXE\") and\n process.args in (\"delete\", \"resize\") and process.args : \"shadows*\"\n", "risk_score": 73, "rule_id": "b5ea4bfe-a1b2-421f-9d47-22a75a6f2921", "severity": "high", @@ -42,5 +42,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json new file mode 100644 index 0000000000000..43dce4acf4df8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic", + "Austin Songer" + ], + "description": "Identifies the use of the Win32_ShadowCopy class and related cmdlets to achieve shadow copy deletion. This commonly occurs in tandem with ransomware or other destructive attacks.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Volume Shadow Copy Deletion via PowerShell", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : (\"powershell.exe\", \"pwsh.exe\") and \n process.args : (\"*Get-WmiObject*\", \"*gwmi*\", \"*Get-CimInstance*\", \"*gcim*\") and\n process.args : (\"*Win32_ShadowCopy*\") and\n process.args : (\"*.Delete()*\", \"*Remove-WmiObject*\", \"*rwmi*\", \"*Remove-CimInstance*\", \"*rcim*\")\n", + "references": [ + "https://docs.microsoft.com/en-us/previous-versions/windows/desktop/vsswmi/win32-shadowcopy", + "https://powershell.one/wmi/root/cimv2/win32_shadowcopy", + "https://www.fortinet.com/blog/threat-research/stomping-shadow-copies-a-second-look-into-deletion-methods" + ], + "risk_score": 73, + "rule_id": "d99a037b-c8e2-47a5-97b9-170d076827c4", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Impact" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "technique": [ + { + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json similarity index 66% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json index e519b23a32b0d..f4f530362a5b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json @@ -21,33 +21,26 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Impact" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" }, "technique": [ { - "id": "T1070", - "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/", - "subtechnique": [ - { - "id": "T1070.004", - "name": "File Deletion", - "reference": "https://attack.mitre.org/techniques/T1070/004/" - } - ] + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 093d5c806c282..1c5006f5e6f48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -41,38 +41,38 @@ import rule28 from './command_and_control_vnc_virtual_network_computing_to_the_i import rule29 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; import rule30 from './defense_evasion_clearing_windows_event_logs.json'; import rule31 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; -import rule32 from './defense_evasion_deleting_backup_catalogs_with_wbadmin.json'; -import rule33 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; -import rule34 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; -import rule35 from './defense_evasion_msbuild_making_network_connections.json'; -import rule36 from './defense_evasion_suspicious_certutil_commands.json'; -import rule37 from './defense_evasion_unusual_network_connection_via_rundll32.json'; -import rule38 from './defense_evasion_unusual_process_network_connection.json'; -import rule39 from './defense_evasion_via_filter_manager.json'; -import rule40 from './defense_evasion_volume_shadow_copy_deletion_via_wmic.json'; -import rule41 from './discovery_whoami_command_activity.json'; -import rule42 from './endgame_adversary_behavior_detected.json'; -import rule43 from './endgame_cred_dumping_detected.json'; -import rule44 from './endgame_cred_dumping_prevented.json'; -import rule45 from './endgame_cred_manipulation_detected.json'; -import rule46 from './endgame_cred_manipulation_prevented.json'; -import rule47 from './endgame_exploit_detected.json'; -import rule48 from './endgame_exploit_prevented.json'; -import rule49 from './endgame_malware_detected.json'; -import rule50 from './endgame_malware_prevented.json'; -import rule51 from './endgame_permission_theft_detected.json'; -import rule52 from './endgame_permission_theft_prevented.json'; -import rule53 from './endgame_process_injection_detected.json'; -import rule54 from './endgame_process_injection_prevented.json'; -import rule55 from './endgame_ransomware_detected.json'; -import rule56 from './endgame_ransomware_prevented.json'; -import rule57 from './execution_command_prompt_connecting_to_the_internet.json'; -import rule58 from './execution_command_shell_started_by_svchost.json'; -import rule59 from './execution_html_help_executable_program_connecting_to_the_internet.json'; -import rule60 from './execution_psexec_lateral_movement_command.json'; -import rule61 from './execution_register_server_program_connecting_to_the_internet.json'; -import rule62 from './execution_via_compiled_html_file.json'; -import rule63 from './impact_volume_shadow_copy_deletion_via_vssadmin.json'; +import rule32 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; +import rule33 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; +import rule34 from './defense_evasion_msbuild_making_network_connections.json'; +import rule35 from './defense_evasion_suspicious_certutil_commands.json'; +import rule36 from './defense_evasion_unusual_network_connection_via_rundll32.json'; +import rule37 from './defense_evasion_unusual_process_network_connection.json'; +import rule38 from './defense_evasion_via_filter_manager.json'; +import rule39 from './discovery_whoami_command_activity.json'; +import rule40 from './endgame_adversary_behavior_detected.json'; +import rule41 from './endgame_cred_dumping_detected.json'; +import rule42 from './endgame_cred_dumping_prevented.json'; +import rule43 from './endgame_cred_manipulation_detected.json'; +import rule44 from './endgame_cred_manipulation_prevented.json'; +import rule45 from './endgame_exploit_detected.json'; +import rule46 from './endgame_exploit_prevented.json'; +import rule47 from './endgame_malware_detected.json'; +import rule48 from './endgame_malware_prevented.json'; +import rule49 from './endgame_permission_theft_detected.json'; +import rule50 from './endgame_permission_theft_prevented.json'; +import rule51 from './endgame_process_injection_detected.json'; +import rule52 from './endgame_process_injection_prevented.json'; +import rule53 from './endgame_ransomware_detected.json'; +import rule54 from './endgame_ransomware_prevented.json'; +import rule55 from './execution_command_prompt_connecting_to_the_internet.json'; +import rule56 from './execution_command_shell_started_by_svchost.json'; +import rule57 from './execution_html_help_executable_program_connecting_to_the_internet.json'; +import rule58 from './execution_psexec_lateral_movement_command.json'; +import rule59 from './execution_register_server_program_connecting_to_the_internet.json'; +import rule60 from './execution_via_compiled_html_file.json'; +import rule61 from './impact_deleting_backup_catalogs_with_wbadmin.json'; +import rule62 from './impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json'; +import rule63 from './impact_volume_shadow_copy_deletion_via_wmic.json'; import rule64 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; import rule65 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; import rule66 from './initial_access_script_executing_powershell.json'; @@ -95,7 +95,7 @@ import rule82 from './persistence_system_shells_via_services.json'; import rule83 from './persistence_user_account_creation.json'; import rule84 from './persistence_via_application_shimming.json'; import rule85 from './privilege_escalation_unusual_parentchild_relationship.json'; -import rule86 from './defense_evasion_modification_of_boot_config.json'; +import rule86 from './impact_modification_of_boot_config.json'; import rule87 from './privilege_escalation_uac_bypass_event_viewer.json'; import rule88 from './defense_evasion_msxsl_network.json'; import rule89 from './discovery_net_command_system_account.json'; @@ -328,7 +328,7 @@ import rule315 from './command_and_control_cobalt_strike_default_teamserver_cert import rule316 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; import rule317 from './defense_evasion_execution_lolbas_wuauclt.json'; import rule318 from './privilege_escalation_unusual_svchost_childproc_childless.json'; -import rule319 from './lateral_movement_rdp_tunnel_plink.json'; +import rule319 from './command_and_control_rdp_tunnel_plink.json'; import rule320 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; import rule321 from './persistence_ms_office_addins_file.json'; import rule322 from './discovery_adfind_command_activity.json'; @@ -428,8 +428,8 @@ import rule415 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json' import rule416 from './credential_access_lsass_memdump_file_created.json'; import rule417 from './lateral_movement_incoming_winrm_shell_execution.json'; import rule418 from './lateral_movement_powershell_remoting_target.json'; -import rule419 from './defense_evasion_hide_encoded_executable_registry.json'; -import rule420 from './defense_evasion_port_forwarding_added_registry.json'; +import rule419 from './command_and_control_port_forwarding_added_registry.json'; +import rule420 from './defense_evasion_hide_encoded_executable_registry.json'; import rule421 from './lateral_movement_rdp_enabled_registry.json'; import rule422 from './privilege_escalation_printspooler_registry_copyfiles.json'; import rule423 from './privilege_escalation_rogue_windir_environment_var.json'; @@ -443,7 +443,7 @@ import rule430 from './credential_access_microsoft_365_brute_force_user_account_ import rule431 from './microsoft_365_teams_custom_app_interaction_allowed.json'; import rule432 from './persistence_microsoft_365_teams_external_access_enabled.json'; import rule433 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; -import rule434 from './defense_evasion_stop_process_service_threshold.json'; +import rule434 from './impact_stop_process_service_threshold.json'; import rule435 from './collection_winrar_encryption.json'; import rule436 from './defense_evasion_unusual_dir_ads.json'; import rule437 from './discovery_admin_recon.json'; @@ -466,8 +466,8 @@ import rule453 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.js import rule454 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; import rule455 from './initial_access_azure_active_directory_powershell_signin.json'; import rule456 from './collection_email_powershell_exchange_mailbox.json'; -import rule457 from './collection_persistence_powershell_exch_mailbox_activesync_add_device.json'; -import rule458 from './execution_scheduled_task_powershell_source.json'; +import rule457 from './execution_scheduled_task_powershell_source.json'; +import rule458 from './persistence_powershell_exch_mailbox_activesync_add_device.json'; import rule459 from './persistence_docker_shortcuts_plist_modification.json'; import rule460 from './persistence_evasion_hidden_local_account_creation.json'; import rule461 from './persistence_finder_sync_plugin_pluginkit.json'; @@ -551,36 +551,54 @@ import rule538 from './persistence_ec2_security_group_configuration_change_detec import rule539 from './defense_evasion_disabling_windows_logs.json'; import rule540 from './persistence_route_53_domain_transfer_lock_disabled.json'; import rule541 from './persistence_route_53_domain_transferred_to_another_account.json'; -import rule542 from './credential_access_user_excessive_sso_logon_errors.json'; -import rule543 from './defense_evasion_suspicious_execution_from_mounted_device.json'; -import rule544 from './defense_evasion_unusual_network_connection_via_dllhost.json'; -import rule545 from './defense_evasion_amsienable_key_mod.json'; -import rule546 from './impact_rds_group_deletion.json'; -import rule547 from './persistence_rds_group_creation.json'; -import rule548 from './exfiltration_rds_snapshot_export.json'; -import rule549 from './persistence_rds_instance_creation.json'; -import rule550 from './ml_auth_rare_hour_for_a_user_to_logon.json'; -import rule551 from './ml_auth_rare_source_ip_for_a_user.json'; -import rule552 from './ml_auth_rare_user_logon.json'; -import rule553 from './ml_auth_spike_in_failed_logon_events.json'; -import rule554 from './ml_auth_spike_in_logon_events.json'; -import rule555 from './ml_auth_spike_in_logon_events_from_a_source_ip.json'; -import rule556 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; -import rule557 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; -import rule558 from './privilege_escalation_printspooler_malicious_driver_file_changes.json'; -import rule559 from './privilege_escalation_printspooler_malicious_registry_modification.json'; -import rule560 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; -import rule561 from './privilege_escalation_unusual_printspooler_childprocess.json'; -import rule562 from './defense_evasion_disabling_windows_defender_powershell.json'; -import rule563 from './defense_evasion_enable_network_discovery_with_netsh.json'; -import rule564 from './defense_evasion_execution_windefend_unusual_path.json'; -import rule565 from './defense_evasion_agent_spoofing_mismatched_id.json'; -import rule566 from './defense_evasion_agent_spoofing_multiple_hosts.json'; -import rule567 from './defense_evasion_parent_process_pid_spoofing.json'; -import rule568 from './defense_evasion_defender_exclusion_via_powershell.json'; -import rule569 from './defense_evasion_whitespace_padding_in_command_line.json'; -import rule570 from './persistence_webshell_detection.json'; -import rule571 from './persistence_via_bits_job_notify_command.json'; +import rule542 from './initial_access_okta_user_attempted_unauthorized_access.json'; +import rule543 from './credential_access_user_excessive_sso_logon_errors.json'; +import rule544 from './persistence_exchange_suspicious_mailbox_right_delegation.json'; +import rule545 from './privilege_escalation_new_or_modified_federation_domain.json'; +import rule546 from './privilege_escalation_sts_getsessiontoken_abuse.json'; +import rule547 from './defense_evasion_suspicious_execution_from_mounted_device.json'; +import rule548 from './defense_evasion_unusual_network_connection_via_dllhost.json'; +import rule549 from './defense_evasion_amsienable_key_mod.json'; +import rule550 from './impact_rds_group_deletion.json'; +import rule551 from './persistence_rds_group_creation.json'; +import rule552 from './persistence_route_table_modified_or_deleted.json'; +import rule553 from './exfiltration_rds_snapshot_export.json'; +import rule554 from './persistence_rds_instance_creation.json'; +import rule555 from './ml_auth_rare_hour_for_a_user_to_logon.json'; +import rule556 from './ml_auth_rare_source_ip_for_a_user.json'; +import rule557 from './ml_auth_rare_user_logon.json'; +import rule558 from './ml_auth_spike_in_failed_logon_events.json'; +import rule559 from './ml_auth_spike_in_logon_events.json'; +import rule560 from './ml_auth_spike_in_logon_events_from_a_source_ip.json'; +import rule561 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; +import rule562 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; +import rule563 from './privilege_escalation_printspooler_malicious_driver_file_changes.json'; +import rule564 from './privilege_escalation_printspooler_malicious_registry_modification.json'; +import rule565 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; +import rule566 from './privilege_escalation_unusual_printspooler_childprocess.json'; +import rule567 from './defense_evasion_disabling_windows_defender_powershell.json'; +import rule568 from './defense_evasion_enable_network_discovery_with_netsh.json'; +import rule569 from './defense_evasion_execution_windefend_unusual_path.json'; +import rule570 from './defense_evasion_agent_spoofing_mismatched_id.json'; +import rule571 from './defense_evasion_agent_spoofing_multiple_hosts.json'; +import rule572 from './defense_evasion_parent_process_pid_spoofing.json'; +import rule573 from './impact_microsoft_365_potential_ransomware_activity.json'; +import rule574 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; +import rule575 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; +import rule576 from './defense_evasion_elasticache_security_group_creation.json'; +import rule577 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; +import rule578 from './impact_volume_shadow_copy_deletion_via_powershell.json'; +import rule579 from './defense_evasion_defender_exclusion_via_powershell.json'; +import rule580 from './defense_evasion_whitespace_padding_in_command_line.json'; +import rule581 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; +import rule582 from './persistence_webshell_detection.json'; +import rule583 from './defense_evasion_execution_control_panel_suspicious_args.json'; +import rule584 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; +import rule585 from './discovery_virtual_machine_fingerprinting_grep.json'; +import rule586 from './impact_backup_file_deletion.json'; +import rule587 from './persistence_screensaver_engine_unexpected_child_process.json'; +import rule588 from './persistence_screensaver_plist_file_modification.json'; +import rule589 from './persistence_via_bits_job_notify_command.json'; export const rawRules = [ rule1, @@ -1154,4 +1172,22 @@ export const rawRules = [ rule569, rule570, rule571, + rule572, + rule573, + rule574, + rule575, + rule576, + rule577, + rule578, + rule579, + rule580, + rule581, + rule582, + rule583, + rule584, + rule585, + rule586, + rule587, + rule588, + rule589, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json new file mode 100644 index 0000000000000..31950fc345c0e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies when a user has been restricted from sending email due to exceeding sending limits of the service policies per the Security Compliance Center.", + "false_positives": [ + "A user sending emails using personal distribution folders may trigger the event." + ], + "from": "now-30m", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Microsoft 365 User Restricted from Sending Email", + "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n", + "query": "event.dataset:o365.audit and event.provider:SecurityComplianceCenter and event.category:web and event.action:\"User restricted from sending email\" and event.outcome:success\n", + "references": [ + "https://docs.microsoft.com/en-us/cloud-app-security/anomaly-detection-policy", + "https://docs.microsoft.com/en-us/cloud-app-security/policy-template-reference" + ], + "risk_score": 47, + "rule_id": "0136b315-b566-482f-866c-1d8e2477ba16", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Configuration Audit" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json new file mode 100644 index 0000000000000..222d30723bc9e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json @@ -0,0 +1,74 @@ +{ + "author": [ + "Elastic", + "Austin Songer" + ], + "description": "Identifies when an unauthorized access attempt is made by a user for an Okta application.", + "index": [ + "filebeat-*", + "logs-okta*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Unauthorized Access to an Okta Application", + "note": "## Config\n\nThe Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:okta.system and event.action:app.generic.unauth_app_access_attempt\n", + "risk_score": 21, + "rule_id": "4edd3e1a-3aa0-499b-8147-4d2ea43b1613", + "severity": "low", + "tags": [ + "Elastic", + "Identity", + "Okta", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json index 16486590cb093..17e9195181f3d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious MS Office Child Process", - "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : (\"eqnedt32.exe\", \"excel.exe\", \"fltldr.exe\", \"msaccess.exe\", \"mspub.exe\", \"powerpnt.exe\", \"winword.exe\") and\n process.name : (\"Microsoft.Workflow.Compiler.exe\", \"arp.exe\", \"atbroker.exe\", \"bginfo.exe\", \"bitsadmin.exe\", \"cdb.exe\", \"certutil.exe\",\n \"cmd.exe\", \"cmstp.exe\", \"cscript.exe\", \"csi.exe\", \"dnx.exe\", \"dsget.exe\", \"dsquery.exe\", \"forfiles.exe\", \"fsi.exe\",\n \"ftp.exe\", \"gpresult.exe\", \"hostname.exe\", \"ieexec.exe\", \"iexpress.exe\", \"installutil.exe\", \"ipconfig.exe\", \"mshta.exe\",\n \"msxsl.exe\", \"nbtstat.exe\", \"net.exe\", \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"odbcconf.exe\", \"ping.exe\",\n \"powershell.exe\", \"pwsh.exe\", \"qprocess.exe\", \"quser.exe\", \"qwinsta.exe\", \"rcsi.exe\", \"reg.exe\", \"regasm.exe\", \"regsvcs.exe\",\n \"regsvr32.exe\", \"sc.exe\", \"schtasks.exe\", \"systeminfo.exe\", \"tasklist.exe\", \"tracert.exe\", \"whoami.exe\",\n \"wmic.exe\", \"wscript.exe\", \"xwizard.exe\", \"explorer.exe\", \"rundll32.exe\", \"hh.exe\")\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : (\"eqnedt32.exe\", \"excel.exe\", \"fltldr.exe\", \"msaccess.exe\", \"mspub.exe\", \"powerpnt.exe\", \"winword.exe\") and\n process.name : (\"Microsoft.Workflow.Compiler.exe\", \"arp.exe\", \"atbroker.exe\", \"bginfo.exe\", \"bitsadmin.exe\", \"cdb.exe\", \"certutil.exe\",\n \"cmd.exe\", \"cmstp.exe\", \"control.exe\", \"cscript.exe\", \"csi.exe\", \"dnx.exe\", \"dsget.exe\", \"dsquery.exe\", \"forfiles.exe\", \n \"fsi.exe\", \"ftp.exe\", \"gpresult.exe\", \"hostname.exe\", \"ieexec.exe\", \"iexpress.exe\", \"installutil.exe\", \"ipconfig.exe\", \n \"mshta.exe\", \"msxsl.exe\", \"nbtstat.exe\", \"net.exe\", \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"odbcconf.exe\", \n \"ping.exe\", \"powershell.exe\", \"pwsh.exe\", \"qprocess.exe\", \"quser.exe\", \"qwinsta.exe\", \"rcsi.exe\", \"reg.exe\", \"regasm.exe\", \n \"regsvcs.exe\", \"regsvr32.exe\", \"sc.exe\", \"schtasks.exe\", \"systeminfo.exe\", \"tasklist.exe\", \"tracert.exe\", \"whoami.exe\",\n \"wmic.exe\", \"wscript.exe\", \"xwizard.exe\", \"explorer.exe\", \"rundll32.exe\", \"hh.exe\")\n", "risk_score": 47, "rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "severity": "medium", @@ -49,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json index 82fa9d8d72a92..0fd10fc807846 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json @@ -60,12 +60,19 @@ { "id": "T1558", "name": "Steal or Forge Kerberos Tickets", - "reference": "https://attack.mitre.org/techniques/T1558/" + "reference": "https://attack.mitre.org/techniques/T1558/", + "subtechnique": [ + { + "id": "T1558.003", + "name": "Kerberoasting", + "reference": "https://attack.mitre.org/techniques/T1558/003/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json index 707596aa333d0..f832eb51336f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Incoming DCOM Lateral Movement via MSHTA", - "query": "sequence with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and\n process.name : \"mshta.exe\" and process.args : \"-Embedding\"\n ] by host.id, process.entity_id\n [network where event.type == \"start\" and process.name : \"mshta.exe\" and \n network.direction == \"incoming\" and network.transport == \"tcp\" and\n source.port > 49151 and destination.port > 49151 and not source.address in (\"127.0.0.1\", \"::1\")\n ] by host.id, process.entity_id\n", + "query": "sequence with maxspan=1m\n [process where event.type in (\"start\", \"process_started\") and\n process.name : \"mshta.exe\" and process.args : \"-Embedding\"\n ] by host.id, process.entity_id\n [network where event.type == \"start\" and process.name : \"mshta.exe\" and \n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\" and\n source.port > 49151 and destination.port > 49151 and not source.address in (\"127.0.0.1\", \"::1\")\n ] by host.id, process.entity_id\n", "references": [ "https://codewhitesec.blogspot.com/2018/07/lethalhta.html" ], @@ -38,11 +38,40 @@ { "id": "T1021", "name": "Remote Services", - "reference": "https://attack.mitre.org/techniques/T1021/" + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.003", + "name": "Distributed Component Object Model", + "reference": "https://attack.mitre.org/techniques/T1021/003/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1218", + "name": "Signed Binary Proxy Execution", + "reference": "https://attack.mitre.org/techniques/T1218/", + "subtechnique": [ + { + "id": "T1218.005", + "name": "Mshta", + "reference": "https://attack.mitre.org/techniques/T1218/005/" + } + ] } ] } ], "type": "eql", - "version": 2 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json index c78343223a10f..8cb2e2c3690e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Incoming DCOM Lateral Movement with MMC", - "query": "sequence by host.id with maxspan=1m\n [network where event.type == \"start\" and process.name : \"mmc.exe\" and\n source.port >= 49152 and destination.port >= 49152 and source.address not in (\"127.0.0.1\", \"::1\") and\n network.direction == \"incoming\" and network.transport == \"tcp\"\n ] by process.entity_id\n [process where event.type in (\"start\", \"process_started\") and process.parent.name : \"mmc.exe\"\n ] by process.parent.entity_id\n", + "query": "sequence by host.id with maxspan=1m\n [network where event.type == \"start\" and process.name : \"mmc.exe\" and\n source.port >= 49152 and destination.port >= 49152 and source.address not in (\"127.0.0.1\", \"::1\") and\n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\"\n ] by process.entity_id\n [process where event.type in (\"start\", \"process_started\") and process.parent.name : \"mmc.exe\"\n ] by process.parent.entity_id\n", "references": [ "https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/" ], @@ -38,11 +38,18 @@ { "id": "T1021", "name": "Remote Services", - "reference": "https://attack.mitre.org/techniques/T1021/" + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.003", + "name": "Distributed Component Object Model", + "reference": "https://attack.mitre.org/techniques/T1021/003/" + } + ] } ] } ], "type": "eql", - "version": 2 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json index 617cbc2fab05e..9ca759cc2facd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Incoming DCOM Lateral Movement with ShellBrowserWindow or ShellWindows", - "query": "sequence by host.id with maxspan=5s\n [network where event.type == \"start\" and process.name : \"explorer.exe\" and\n network.direction == \"incoming\" and network.transport == \"tcp\" and\n source.port > 49151 and destination.port > 49151 and not source.address in (\"127.0.0.1\", \"::1\")\n ] by process.entity_id\n [process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"explorer.exe\"\n ] by process.parent.entity_id\n", + "query": "sequence by host.id with maxspan=5s\n [network where event.type == \"start\" and process.name : \"explorer.exe\" and\n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\" and\n source.port > 49151 and destination.port > 49151 and not source.address in (\"127.0.0.1\", \"::1\")\n ] by process.entity_id\n [process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"explorer.exe\"\n ] by process.parent.entity_id\n", "references": [ "https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/" ], @@ -38,11 +38,18 @@ { "id": "T1021", "name": "Remote Services", - "reference": "https://attack.mitre.org/techniques/T1021/" + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.003", + "name": "Distributed Component Object Model", + "reference": "https://attack.mitre.org/techniques/T1021/003/" + } + ] } ] } ], "type": "eql", - "version": 2 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json index b4534c48d0fa2..c9983d2ba186e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json @@ -33,13 +33,20 @@ }, "technique": [ { - "id": "T1210", - "name": "Exploitation of Remote Services", - "reference": "https://attack.mitre.org/techniques/T1210/" + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.002", + "name": "SMB/Windows Admin Shares", + "reference": "https://attack.mitre.org/techniques/T1021/002/" + } + ] } ] } ], "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json index b34badc7c8611..6e11258e23d00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json @@ -13,7 +13,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Abnormally Large DNS Response", - "note": "## Triage and analysis\n\n### Investigating Large DNS Responses\nDetection alerts from this rule indicate an attempt was made to exploit CVE-2020-1350 (SigRed) through the use of large DNS responses on a Windows DNS server. Here are some possible avenues of investigation:\n- Investigate any corresponding Intrusion Detection Signatures (IDS) alerts that can validate this detection alert.\n- Examine the `dns.question_type` network fieldset with a protocol analyzer, such as Zeek, Packetbeat, or Suricata, for `SIG` or `RRSIG` data.\n- Validate the patch level and OS of the targeted DNS server to validate the observed activity was not large-scale Internet vulnerability scanning.\n- Validate that the source of the network activity was not from an authorized vulnerability scan or compromise assessment.", + "note": "## Triage and analysis\n\n### Investigating Large DNS Responses\nDetection alerts from this rule indicate possible anomalous activity around large byte DNS responses from a Windows DNS\nserver. This detection rule was created based on activity represented in exploitation of vulnerability (CVE-2020-1350)\nalso known as [SigRed](https://www.elastic.co/blog/detection-rules-for-sigred-vulnerability) during July 2020.\n\n#### Possible investigation steps:\n- This specific rule is sourced from network log activity such as DNS or network level data. It's important to validate\nthe source of the incoming traffic and determine if this activity has been observed previously within an environment.\n- Activity can be further investigated and validated by reviewing available corresponding Intrusion Detection Signatures (IDS) alerts associated with activity.\n- Further examination can be made by reviewing the `dns.question_type` network fieldset with a protocol analyzer, such as Zeek, Packetbeat, or Suricata, for `SIG` or `RRSIG` data.\n- Validate the patch level and OS of the targeted DNS server to validate the observed activity was not large-scale Internet vulnerability scanning.\n- Validate that the source of the network activity was not from an authorized vulnerability scan or compromise assessment.\n\n#### False Positive Analysis\n- Based on this rule which looks for a threshold of 60k bytes, it is possible for activity to be generated under 65k bytes\nand related to legitimate behavior. In packet capture files received by the [SANS Internet Storm Center](https://isc.sans.edu/forums/diary/PATCH+NOW+SIGRed+CVE20201350+Microsoft+DNS+Server+Vulnerability/26356/), byte responses\nwere all observed as greater than 65k bytes.\n- This activity has the ability to be triggered from compliance/vulnerability scanning or compromise assessment, it's\nimportant to determine the source of the activity and potential whitelist the source host\n\n\n### Related Rules\n- Unusual Child Process of dns.exe\n- Unusual File Modification by dns.exe\n\n### Response and Remediation\n- Review and implement the above detection logic within your environment using technology such as Endpoint security, Winlogbeat, Packetbeat, or network security monitoring (NSM) platforms such as Zeek or Suricata.\n- Ensure that you have deployed the latest Microsoft [Security Update](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350) (Monthly Rollup or Security Only) and restart the\npatched machines. If unable to patch immediately: Microsoft [released](https://support.microsoft.com/en-us/help/4569509/windows-dns-server-remote-code-execution-vulnerability) a registry-based workaround that doesn\u2019t require a\nrestart. This can be used as a temporary solution before the patch is applied.\n- Maintain backups of your critical systems to aid in quick recovery.\n- Perform routine vulnerability scans of your systems, monitor [CISA advisories](https://us-cert.cisa.gov/ncas/current-activity) and patch identified vulnerabilities.\n- If observed true positive activity, implement a remediation plan and monitor host-based artifacts for additional post-exploitation behavior.\n", "query": "event.category:(network or network_traffic) and destination.port:53 and\n (event.dataset:zeek.dns or type:dns or event.type:connection) and network.bytes > 60000\n", "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", @@ -48,5 +48,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json index 8173ddc6f1003..5fe9d066bc76d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Lateral Tool Transfer", - "query": "sequence by host.id with maxspan=30s\n [network where event.type == \"start\" and process.pid == 4 and destination.port == 445 and\n network.direction == \"incoming\" and network.transport == \"tcp\" and\n source.address != \"127.0.0.1\" and source.address != \"::1\"\n ] by process.entity_id\n /* add more executable extensions here if they are not noisy in your environment */\n [file where event.type in (\"creation\", \"change\") and process.pid == 4 and file.extension : (\"exe\", \"dll\", \"bat\", \"cmd\")] by process.entity_id\n", + "query": "sequence by host.id with maxspan=30s\n [network where event.type == \"start\" and process.pid == 4 and destination.port == 445 and\n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\" and\n source.address != \"127.0.0.1\" and source.address != \"::1\"\n ] by process.entity_id\n /* add more executable extensions here if they are not noisy in your environment */\n [file where event.type in (\"creation\", \"change\") and process.pid == 4 and file.extension : (\"exe\", \"dll\", \"bat\", \"cmd\")] by process.entity_id\n", "risk_score": 47, "rule_id": "58bc134c-e8d2-4291-a552-b4b3e537c60b", "severity": "medium", @@ -41,5 +41,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json index 062013549e1da..04a60f99556f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Incoming Execution via WinRM Remote Shell", - "query": "sequence by host.id with maxspan=30s\n [network where process.pid == 4 and network.direction == \"incoming\" and\n destination.port in (5985, 5986) and network.protocol == \"http\" and not source.address in (\"::1\", \"127.0.0.1\")\n ]\n [process where event.type == \"start\" and process.parent.name : \"winrshost.exe\" and not process.name : \"conhost.exe\"]\n", + "query": "sequence by host.id with maxspan=30s\n [network where process.pid == 4 and network.direction : (\"incoming\", \"ingress\") and\n destination.port in (5985, 5986) and network.protocol == \"http\" and not source.address in (\"::1\", \"127.0.0.1\")\n ]\n [process where event.type == \"start\" and process.parent.name : \"winrshost.exe\" and not process.name : \"conhost.exe\"]\n", "risk_score": 47, "rule_id": "1cd01db9-be24-4bef-8e7c-e923f0ff78ab", "severity": "medium", @@ -44,5 +44,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json index 901a19d896ff3..9b13ade43812f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "WMI Incoming Lateral Movement", - "query": "sequence by host.id with maxspan = 2s\n\n /* Accepted Incoming RPC connection by Winmgmt service */\n\n [network where process.name : \"svchost.exe\" and network.direction == \"incoming\" and\n source.address != \"127.0.0.1\" and source.address != \"::1\" and \n source.port >= 49152 and destination.port >= 49152\n ]\n\n /* Excluding Common FPs Nessus and SCCM */\n\n [process where event.type in (\"start\", \"process_started\") and process.parent.name : \"WmiPrvSE.exe\" and\n not process.args : (\"C:\\\\windows\\\\temp\\\\nessus_*.txt\", \n \"C:\\\\windows\\\\TEMP\\\\nessus_*.TMP\", \n \"C:\\\\Windows\\\\CCM\\\\SystemTemp\\\\*\", \n \"C:\\\\Windows\\\\CCMCache\\\\*\", \n \"C:\\\\CCM\\\\Cache\\\\*\")\n ]\n", + "query": "sequence by host.id with maxspan = 2s\n\n /* Accepted Incoming RPC connection by Winmgmt service */\n\n [network where process.name : \"svchost.exe\" and network.direction : (\"incoming\", \"ingress\") and\n source.address != \"127.0.0.1\" and source.address != \"::1\" and \n source.port >= 49152 and destination.port >= 49152\n ]\n\n /* Excluding Common FPs Nessus and SCCM */\n\n [process where event.type in (\"start\", \"process_started\") and process.parent.name : \"WmiPrvSE.exe\" and\n not process.args : (\"C:\\\\windows\\\\temp\\\\nessus_*.txt\", \n \"C:\\\\windows\\\\TEMP\\\\nessus_*.TMP\", \n \"C:\\\\Windows\\\\CCM\\\\SystemTemp\\\\*\", \n \"C:\\\\Windows\\\\CCMCache\\\\*\", \n \"C:\\\\CCM\\\\Cache\\\\*\")\n ]\n", "risk_score": 47, "rule_id": "f3475224-b179-4f78-8877-c2bd64c26b88", "severity": "medium", @@ -50,5 +50,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json index 33b5ef7c0dacb..94708f90d20bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Incoming Execution via PowerShell Remoting", - "query": "sequence by host.id with maxspan = 30s\n [network where network.direction == \"incoming\" and destination.port in (5985, 5986) and\n network.protocol == \"http\" and source.address != \"127.0.0.1\" and source.address != \"::1\"\n ]\n [process where event.type == \"start\" and process.parent.name : \"wsmprovhost.exe\" and not process.name : \"conhost.exe\"]\n", + "query": "sequence by host.id with maxspan = 30s\n [network where network.direction : (\"incoming\", \"ingress\") and destination.port in (5985, 5986) and\n network.protocol == \"http\" and source.address != \"127.0.0.1\" and source.address != \"::1\"\n ]\n [process where event.type == \"start\" and process.parent.name : \"wsmprovhost.exe\" and not process.name : \"conhost.exe\"]\n", "references": [ "https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.1" ], @@ -47,5 +47,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json index 6b2f782e488c4..584f24cfb30f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json @@ -35,12 +35,19 @@ { "id": "T1021", "name": "Remote Services", - "reference": "https://attack.mitre.org/techniques/T1021/" + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.001", + "name": "Remote Desktop Protocol", + "reference": "https://attack.mitre.org/techniques/T1021/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json index 0318883e374d3..0e5b7e7bc9001 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential SharpRDP Behavior", - "query": "/* Incoming RDP followed by a new RunMRU string value set to cmd, powershell, taskmgr or tsclient, followed by process execution within 1m */\n\nsequence by host.id with maxspan=1m\n [network where event.type == \"start\" and process.name : \"svchost.exe\" and destination.port == 3389 and \n network.direction == \"incoming\" and network.transport == \"tcp\" and\n source.address != \"127.0.0.1\" and source.address != \"::1\"\n ]\n\n [registry where process.name : \"explorer.exe\" and \n registry.path : (\"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\RunMRU\\\\*\") and\n registry.data.strings : (\"cmd.exe*\", \"powershell.exe*\", \"taskmgr*\", \"\\\\\\\\tsclient\\\\*.exe\\\\*\")\n ]\n \n [process where event.type in (\"start\", \"process_started\") and\n (process.parent.name : (\"cmd.exe\", \"powershell.exe\", \"taskmgr.exe\") or process.args : (\"\\\\\\\\tsclient\\\\*.exe\")) and \n not process.name : \"conhost.exe\"\n ]\n", + "query": "/* Incoming RDP followed by a new RunMRU string value set to cmd, powershell, taskmgr or tsclient, followed by process execution within 1m */\n\nsequence by host.id with maxspan=1m\n [network where event.type == \"start\" and process.name : \"svchost.exe\" and destination.port == 3389 and \n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\" and\n source.address != \"127.0.0.1\" and source.address != \"::1\"\n ]\n\n [registry where process.name : \"explorer.exe\" and \n registry.path : (\"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\RunMRU\\\\*\") and\n registry.data.strings : (\"cmd.exe*\", \"powershell.exe*\", \"taskmgr*\", \"\\\\\\\\tsclient\\\\*.exe\\\\*\")\n ]\n \n [process where event.type in (\"start\", \"process_started\") and\n (process.parent.name : (\"cmd.exe\", \"powershell.exe\", \"taskmgr.exe\") or process.args : (\"\\\\\\\\tsclient\\\\*.exe\")) and \n not process.name : \"conhost.exe\"\n ]\n", "references": [ "https://posts.specterops.io/revisiting-remote-desktop-lateral-movement-8fb905cb46c3", "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Lateral%20Movement/LM_sysmon_3_12_13_1_SharpRDP.evtx" @@ -39,11 +39,18 @@ { "id": "T1021", "name": "Remote Services", - "reference": "https://attack.mitre.org/techniques/T1021/" + "reference": "https://attack.mitre.org/techniques/T1021/", + "subtechnique": [ + { + "id": "T1021.001", + "name": "Remote Desktop Protocol", + "reference": "https://attack.mitre.org/techniques/T1021/001/" + } + ] } ] } ], "type": "eql", - "version": 2 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json index 88f5e0e63a052..5220506d37f58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remotely Started Services via RPC", - "query": "sequence with maxspan=1s\n [network where process.name : \"services.exe\" and\n network.direction == \"incoming\" and network.transport == \"tcp\" and \n source.port >= 49152 and destination.port >= 49152 and source.address not in (\"127.0.0.1\", \"::1\")\n ] by host.id, process.entity_id\n\n [process where event.type in (\"start\", \"process_started\") and process.parent.name : \"services.exe\" and \n not (process.name : \"svchost.exe\" and process.args : \"tiledatamodelsvc\") and \n not (process.name : \"msiexec.exe\" and process.args : \"/V\")\n \n /* uncomment if psexec is noisy in your environment */\n /* and not process.name : \"PSEXESVC.exe\" */\n ] by host.id, process.parent.entity_id\n", + "query": "sequence with maxspan=1s\n [network where process.name : \"services.exe\" and\n network.direction : (\"incoming\", \"ingress\") and network.transport == \"tcp\" and \n source.port >= 49152 and destination.port >= 49152 and source.address not in (\"127.0.0.1\", \"::1\")\n ] by host.id, process.entity_id\n\n [process where event.type in (\"start\", \"process_started\") and process.parent.name : \"services.exe\" and \n not (process.name : \"svchost.exe\" and process.args : \"tiledatamodelsvc\") and \n not (process.name : \"msiexec.exe\" and process.args : \"/V\")\n \n /* uncomment if psexec is noisy in your environment */\n /* and not process.name : \"PSEXESVC.exe\" */\n ] by host.id, process.parent.entity_id\n", "risk_score": 47, "rule_id": "aa9a274d-6b53-424d-ac5e-cb8ca4251650", "severity": "medium", @@ -41,5 +41,5 @@ } ], "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json index b66b5a94fe27f..b60717e61765a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json @@ -12,8 +12,8 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote Scheduled Task Creation", - "note": "## Triage and analysis\n\nDecode the base64 encoded tasks actions registry value to investigate the task configured action.", - "query": "/* Task Scheduler service incoming connection followed by TaskCache registry modification */\n\nsequence by host.id, process.entity_id with maxspan = 1m\n [network where process.name : \"svchost.exe\" and\n network.direction == \"incoming\" and source.port >= 49152 and destination.port >= 49152 and\n source.address != \"127.0.0.1\" and source.address != \"::1\"\n ]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", + "note": "## Triage and analysis\n\n### Investigating Creation of Remote Scheduled Tasks\n\n[Scheduled tasks](https://docs.microsoft.com/en-us/windows/win32/taskschd/about-the-task-scheduler) are a great mechanism used for persistence and executing programs. These features can\nbe used remotely for a variety of legitimate reasons, but at the same time used by malware and adversaries.\nWhen investigating scheduled tasks that have been set-up remotely, one of the first methods should be determining the\noriginal intent behind the configuration and verify if the activity is tied to benign behavior such as software installations or any kind\nof network administrator work. One objective for these alerts is to understand the configured action within the scheduled\ntask, this is captured within the registry event data for this rule and can be base64 decoded to view the value.\n\n#### Possible investigation steps:\n- Review the base64 encoded tasks actions registry value to investigate the task configured action.\n- Determine if task is related to legitimate or benign behavior based on the corresponding process or program tied to the\nscheduled task.\n- Further examination should include both the source and target machines where host-based artifacts and network logs\nshould be reviewed further around the time window of the creation of the scheduled task.\n\n### False Positive Analysis\n- There is a high possibility of benign activity tied to the creation of remote scheduled tasks as it is a general feature\nwithin Windows and used for legitimate purposes for a wide range of activity. Any kind of context should be found to\nfurther understand the source of the activity and determine the intent based on the scheduled task contents.\n\n### Related Rules\n- Service Command Lateral Movement\n- Remotely Started Services via RPC\n\n### Response and Remediation\n- This behavior represents post-exploitation actions such as persistence or lateral movement, immediate response should\nbe taken to review and investigate the activity and potentially isolate involved machines to prevent further post-compromise\nbehavior.\n- Remove scheduled task and any other related artifacts to the activity.\n- Review privileged account management and user account management settings such as implementing GPO policies to further\nrestrict activity or configure settings that only allow Administrators to create remote scheduled tasks.\n", + "query": "/* Task Scheduler service incoming connection followed by TaskCache registry modification */\n\nsequence by host.id, process.entity_id with maxspan = 1m\n [network where process.name : \"svchost.exe\" and\n network.direction : (\"incoming\", \"ingress\") and source.port >= 49152 and destination.port >= 49152 and\n source.address != \"127.0.0.1\" and source.address != \"::1\"\n ]\n [registry where registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\TaskCache\\\\Tasks\\\\*\\\\Actions\"]\n", "risk_score": 47, "rule_id": "954ee7c8-5437-49ae-b2d6-2960883898e9", "severity": "medium", @@ -51,11 +51,18 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "type": "eql", - "version": 3 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json index 2f0a60b3efba9..d5d055bfa1658 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json @@ -3,7 +3,7 @@ "author": [ "Elastic" ], - "description": "A machine learning job found an unusual user name in the authentication logs. An unusual user name is one way of detecting credentialed access by means of a new or dormant user account. A user account that is normally inactive (because the user has left the organization) that becomes active may be due to credentialed access using a compromised account password. Threat actors will sometimes also create new users as a means of persisting in a compromised web application.", + "description": "A machine learning job found an unusual user name in the authentication logs. An unusual user name is one way of detecting credentialed access by means of a new or dormant user account. An inactive user account (because the user has left the organization) that becomes active may be due to credentialed access using a compromised account password. Threat actors will sometimes also create new users as a means of persisting in a compromised web application.", "false_positives": [ "User accounts that are rarely active, such as a site reliability engineer (SRE) or developer logging into a production server for troubleshooting, may trigger this alert. Under some conditions, a newly created user account may briefly trigger this alert while the model is learning." ], @@ -25,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json index 8e007c96c37fb..ee9acc43ac8d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json @@ -3,7 +3,7 @@ "author": [ "Elastic" ], - "description": "A machine learning job found an unusually large spike in successful authentication events events from a particular source IP address. This can be due to password spraying, user enumeration or brute force activity.", + "description": "A machine learning job found an unusually large spike in successful authentication events from a particular source IP address. This can be due to password spraying, user enumeration or brute force activity.", "false_positives": [ "Build servers and CI systems can sometimes trigger this alert. Security test cycles that include brute force or password spraying activities may trigger this alert." ], @@ -25,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json index e9ebbf2470b53..1b64f1d85301a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "high_distinct_count_error_message", "name": "Spike in AWS Error Messages", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating Spikes in CloudTrail Errors\nDetection alerts from this rule indicate a large spike in the number of CloudTrail log messages that contain a particular error message. The error message in question was associated with the response to an AWS API command or method call. Here are some possible avenues of investigation:\n- Examine the history of the error. Has it manifested before? If the error, which is visible in the `aws.cloudtrail.error_message` field, only manifested recently, it might be related to recent changes in an automation module or script.\n- Examine the request parameters. These may provide indications as to the nature of the task being performed when the error occurred. Is the error related to unsuccessful attempts to enumerate or access objects, data, or secrets? If so, this can sometimes be a byproduct of discovery, privilege escalation or lateral movement attempts.\n- Consider the user as identified by the user.name field. Is this activity part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key id in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating Spikes in CloudTrail Errors\n\nCloudTrail logging provides visibility on actions taken within an AWS environment. By monitoring these events and understanding\nwhat is considered normal behavior within an organization, suspicious or malicious activity can be spotted when deviations\nare observed. This example rule triggers from a large spike in the number of CloudTrail log messages that contain a\nparticular error message. The error message in question was associated with the response to an AWS API command or method call,\nthis has the potential to uncover unknown threats or activity.\n\n#### Possible investigation steps:\n- Examine the history of the error. Has it manifested before? If the error, which is visible in the `aws.cloudtrail.error_message` field, only manifested recently, it might be related to recent changes in an automation module or script.\n- Examine the request parameters. These may provide indications as to the nature of the task being performed when the error occurred. Is the error related to unsuccessful attempts to enumerate or access objects, data, or secrets? If so, this can sometimes be a byproduct of discovery, privilege escalation or lateral movement attempts.\n- Consider the user as identified by the `user.name field`. Is this activity part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n\n### False Positive Analysis\n- This rule has the possibility to produce false positives based on unexpected activity occurring such as bugs or recent\nchanges to automation modules or scripting.\n- Adoption of new services or implementing new functionality to scripts may generate false positives\n\n### Related Rules\n- Unusual AWS Command for a User\n- Rare AWS Error Code\n\n### Response and Remediation\n- If activity is observed as suspicious or malicious, immediate response should be looked into rotating and deleting AWS IAM access keys\n- Validate if any unauthorized new users were created, remove these accounts and request password resets for other IAM users\n- Look into enabling multi-factor authentication for users\n- Follow security best practices [outlined](https://aws.amazon.com/premiumsupport/knowledge-center/security-best-practices/) by AWS\n", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json index ac7a867f5cd6e..d9e2b3e358760 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "rare_error_code", "name": "Rare AWS Error Code", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\nInvestigating Unusual CloudTrail Error Activity ###\nDetection alerts from this rule indicate a rare and unusual error code that was associated with the response to an AWS API command or method call. Here are some possible avenues of investigation:\n- Examine the history of the error. Has it manifested before? If the error, which is visible in the `aws.cloudtrail.error_code field`, only manifested recently, it might be related to recent changes in an automation module or script.\n- Examine the request parameters. These may provide indications as to the nature of the task being performed when the error occurred. Is the error related to unsuccessful attempts to enumerate or access objects, data, or secrets? If so, this can sometimes be a byproduct of discovery, privilege escalation, or lateral movement attempts.\n- Consider the user as identified by the `user.name` field. Is this activity part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key id in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\nInvestigating Unusual CloudTrail Error Activity ###\nDetection alerts from this rule indicate a rare and unusual error code that was associated with the response to an AWS API command or method call. Here are some possible avenues of investigation:\n- Examine the history of the error. Has it manifested before? If the error, which is visible in the `aws.cloudtrail.error_code field`, only manifested recently, it might be related to recent changes in an automation module or script.\n- Examine the request parameters. These may provide indications as to the nature of the task being performed when the error occurred. Is the error related to unsuccessful attempts to enumerate or access objects, data, or secrets? If so, this can sometimes be a byproduct of discovery, privilege escalation, or lateral movement attempts.\n- Consider the user as identified by the `user.name` field. Is this activity part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json index 2a31ce8c065d8..a3d6208eb9f05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "rare_method_for_a_city", "name": "Unusual City For an AWS Command", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual CloudTrail Event\nDetection alerts from this rule indicate an AWS API command or method call that is rare and unusual for the geolocation of the source IP address. Here are some possible avenues of investigation:\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key id in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual CloudTrail Event\nDetection alerts from this rule indicate an AWS API command or method call that is rare and unusual for the geolocation of the source IP address. Here are some possible avenues of investigation:\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json index ebe7971e94289..4576b080e1ea6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "rare_method_for_a_country", "name": "Unusual Country For an AWS Command", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual CloudTrail Event\nDetection alerts from this rule indicate an AWS API command or method call that is rare and unusual for the geolocation of the source IP address. Here are some possible avenues of investigation:\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key id in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual Country For an AWS Command\n\nCloudTrail logging provides visibility on actions taken within an AWS environment. By monitoring these events and understanding\nwhat is considered normal behavior within an organization, suspicious or malicious activity can be spotted when deviations\nare observed. This example rule focuses on AWS command activity where the country from the source of the activity has been\nconsidered unusual based on previous history.\n\n#### Possible investigation steps:\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n\n### False Positive Analysis\n- False positives can occur if activity is coming from new employees based in a country with no previous history in AWS,\ntherefore it's important to validate the activity listed in the investigation steps above.\n\n### Related Rules\n- Unusual City For an AWS Command\n- Unusual AWS Command for a User\n- Rare AWS Error Code\n\n### Response and Remediation\n- If activity is observed as suspicious or malicious, immediate response should be looked into rotating and deleting AWS IAM access keys\n- Validate if any unauthorized new users were created, remove these accounts and request password resets for other IAM users\n- Look into enabling multi-factor authentication for users\n- Follow security best practices [outlined](https://aws.amazon.com/premiumsupport/knowledge-center/security-best-practices/) by AWS\n", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json index ab9364c453423..53f9fab8d1b48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "machine_learning_job_id": "rare_method_for_a_username", "name": "Unusual AWS Command for a User", - "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual CloudTrail Event\n\nDetection alerts from this rule indicate an AWS API command or method call that is rare and unusual for the calling IAM user. Here are some possible avenues of investigation:\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key id in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.\n\n## Triage and analysis\n\n### Investigating an Unusual CloudTrail Event\n\nDetection alerts from this rule indicate an AWS API command or method call that is rare and unusual for the calling IAM user. Here are some possible avenues of investigation:\n- Consider the user as identified by the `user.name` field. Is this command part of an expected workflow for the user context? Examine the user identity in the `aws.cloudtrail.user_identity.arn` field and the access key ID in the `aws.cloudtrail.user_identity.access_key_id` field, which can help identify the precise user context. The user agent details in the `user_agent.original` field may also indicate what kind of a client made the request.\n- Consider the source IP address and geolocation for the calling user who issued the command. Do they look normal for the calling user? If the source is an EC2 IP address, is it associated with an EC2 instance in one of your accounts, or could it be sourcing from an EC2 instance that's not under your control? If it is an authorized EC2 instance, is the activity associated with normal behavior for the instance role or roles? Are there any other alerts or signs of suspicious activity involving this instance?\n- Consider the time of day. If the user is a human, not a program or script, did the activity take place during a normal time of day?\n- Examine the history of the command. If the command, which is visible in the `event.action field`, only manifested recently, it might be part of a new automation module or script. If it has a consistent cadence (for example, if it appears in small numbers on a weekly or monthly cadence), it might be part of a housekeeping or maintenance process.\n- Examine the request parameters. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -26,5 +26,5 @@ "ML" ], "type": "machine_learning", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json index 8729de9a8689d..d8bf26884b16f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_windows.json @@ -15,7 +15,7 @@ "v2_rare_process_by_host_windows_ecs" ], "name": "Unusual Process For a Windows Host", - "note": "## Triage and analysis\n\n### Investigating an Unusual Windows Process\nDetection alerts from this rule indicate the presence of a Windows process that is rare and unusual for the host it ran on. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process only manifested recently, it might be part of a new software package. If it has a consistent cadence (for example if it runs monthly or quarterly), it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package.\n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools. ", + "note": "## Triage and analysis\n\n### Investigating an Unusual Windows Process\n\nSearching for abnormal Windows processes is a good methodology to find potentially malicious activity within a network.\nBy understanding what is commonly run within an environment and developing baselines for legitimate activity can help\nuncover potential malware and suspicious behaviors.\n\n#### Possible investigation steps:\n- Consider the user as identified by the `user.name` field. Is this program part of an expected workflow for the user who ran this program on this host?\n- Examine the history of execution. If this process only manifested recently, it might be part of a new software package. If it has a consistent cadence (for example if it runs monthly or quarterly), it might be part of a monthly or quarterly business process.\n- Examine the process metadata like the values of the Company, Description and Product fields which may indicate whether the program is associated with an expected software vendor or package.\n- Examine arguments and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.\n- Consider the same for the parent process. If the parent process is a legitimate system utility or service, this could be related to software updates or system management. If the parent process is something user-facing like an Office application, this process could be more suspicious.\n- If you have file hash values in the event data, and you suspect malware, you can optionally run a search for the file hash to see if the file is identified as malware by anti-malware tools.\n\n### False Positive Analysis\n- Validate the unusual Windows process is not related to new benign software installation activity. If related to\nlegitimate software, this can be done by leveraging the exception workflow in the Kibana Security App or Elasticsearch\nAPI to tune this rule to your environment\n- Try to understand the context of the execution by thinking about the user, machine, or business purpose. It's possible that a small number of endpoints\nsuch as servers that have very unique software that might appear to be unusual, but satisfy a specific business need.\n\n### Related Rules\n- Anomalous Windows Process Creation\n- Unusual Windows Path Activity\n- Unusual Windows Process Calling the Metadata Service\n\n### Response and Remediation\n- This rule is related to process execution events and should be immediately reviewed and investigated to determine if malicious\n- Based on validation and if malicious, the impacted machine should be isolated and analyzed to determine other post-compromise\nbehavior such as setting up persistence or performing lateral movement.\n- Look into preventive measures such as Windows Defender Application Control and AppLocker to gain better control on\nwhat is allowed to run on Windows infrastructure.\n", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" ], @@ -30,5 +30,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json index a3468f4a68948..b7421934ba8e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json @@ -3,7 +3,7 @@ "Elastic", "Austin Songer" ], - "description": "Identifies a change to an AWS Security Group Configuration. A security group is like a virtul firewall and modifying configurations may allow unauthorized access. Threat actors may abuse this to establish persistence, exfiltrate data, or pivot in a AWS environment.", + "description": "Identifies a change to an AWS Security Group Configuration. A security group is like a virtual firewall, and modifying configurations may allow unauthorized access. Threat actors may abuse this to establish persistence, exfiltrate data, or pivot in an AWS environment.", "false_positives": [ "A security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." ], @@ -49,10 +49,23 @@ "name": "Defense Evasion", "reference": "https://attack.mitre.org/tactics/TA0005/" }, - "technique": [] + "technique": [ + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.007", + "name": "Disable or Modify Cloud Firewall", + "reference": "https://attack.mitre.org/techniques/T1562/007/" + } + ] + } + ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json index 8edaef5dc72fd..24f0f3d4d95b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json @@ -39,12 +39,19 @@ { "id": "T1136", "name": "Create Account", - "reference": "https://attack.mitre.org/techniques/T1136/" + "reference": "https://attack.mitre.org/techniques/T1136/", + "subtechnique": [ + { + "id": "T1136.001", + "name": "Local Account", + "reference": "https://attack.mitre.org/techniques/T1136/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json index 947c1c748af69..21ad9c5161541 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious Startup Shell Folder Modification", - "note": "## Triage and analysis\n\nVerify file creation events in the new Windows Startup folder location.", + "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Activity\n\nTechniques used within malware and by adversaries often leverage the Windows registry to store malicious programs for\npersistence. Startup shell folders are often targeted as they are not as prevalent as normal Startup folder paths so this\nbehavior may evade existing AV/EDR solutions. Another preference is that these programs might run with higher privileges\nwhich can be ideal for an attacker.\n\n#### Possible investigation steps:\n- Review the source process and related file tied to the Windows Registry entry\n- Validate the activity is not related to planned patches, updates, network administrator activity or legitimate software\ninstallations\n- Determine if activity is unique by validating if other machines in same organization have similar entry\n\n### False Positive Analysis\n- There is a high possibility of benign legitimate programs being added to Shell folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before entering further\ninvestigation, this activity should be validated that is it not related to benign activity\n\n### Related Rules\n- Startup or Run Key Registry Modification\n- Persistent Scripts in the Startup Directory\n\n### Response and Remediation\n- Activity should first be validated as a true positive event if so then immediate response should be taken to review,\ninvestigate and potentially isolate activity to prevent further post-compromise behavior\n- The respective binary or program tied to this persistence method should be further analyzed and reviewed to understand\nit's behavior and capabilities\n- Since this activity is considered post-exploitation behavior, it's important to understand how the behavior was first\ninitialized such as through a macro-enabled document that was attached in a phishing email. By understanding the source\nof the attack, this information can then be used to search for similar indicators on other machines in the same environment.\n", "query": "registry where\n registry.path : (\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\User Shell Folders\\\\Common Startup\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\\\\Common Startup\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\User Shell Folders\\\\Startup\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\\\\Startup\"\n ) and\n registry.data.strings != null and\n /* Normal Startup Folder Paths */\n not registry.data.strings : (\n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"%ProgramData%\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"%USERPROFILE%\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\",\n \"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\"\n )\n", "risk_score": 73, "rule_id": "c8b150f0-0164-475b-a75e-74b47800a9ff", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json new file mode 100644 index 0000000000000..e950569f19878 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic", + "Austin Songer" + ], + "description": "Identifies the assignment of rights to accesss content from another mailbox. An adversary may use the compromised account to send messages to other accounts in the network of the target business while creating inbox rules, so messages can evade spam/phishing detection mechanisms.", + "false_positives": [ + "Assignment of rights to a service account." + ], + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "O365 Exchange Suspicious Mailbox Right Delegation", + "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.action:Add-MailboxPermission and \no365.audit.Parameters.AccessRights:(FullAccess or SendAs or SendOnBehalf) and event.outcome:success\n", + "risk_score": 21, + "rule_id": "0ce6487d-8069-4888-9ddd-61b52490cebc", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Configuration Audit" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/", + "subtechnique": [ + { + "id": "T1098.002", + "name": "Exchange Email Delegate Permissions", + "reference": "https://attack.mitre.org/techniques/T1098/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json index 86b1cd3e71eaf..ebbe2448c75df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json @@ -35,12 +35,19 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_job_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_job_creation.json index 6e656209fd055..60afcad90333c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_job_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_job_creation.json @@ -38,12 +38,19 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json index 712e98d4ac941..128fdd9de5575 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json @@ -39,11 +39,18 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json similarity index 72% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json index 9a494a13fa297..75044e20ca5fd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_persistence_powershell_exch_mailbox_activesync_add_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json @@ -28,31 +28,33 @@ "Host", "Windows", "Threat Detection", - "Collection" + "Persistence" ], "threat": [ { "framework": "MITRE ATT&CK", "tactic": { - "id": "TA0009", - "name": "Collection", - "reference": "https://attack.mitre.org/tactics/TA0009/" + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" }, "technique": [ { - "id": "T1114", - "name": "Email Collection", - "reference": "https://attack.mitre.org/techniques/T1114/" - }, - { - "id": "T1005", - "name": "Data from Local System", - "reference": "https://attack.mitre.org/techniques/T1005/" + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/", + "subtechnique": [ + { + "id": "T1098.002", + "name": "Exchange Email Delegate Permissions", + "reference": "https://attack.mitre.org/techniques/T1098/002/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json index aa2c946d3a001..4ea6631025c11 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json @@ -1,6 +1,7 @@ { "author": [ - "Elastic" + "Elastic", + "Austin Songer" ], "description": "Identifies the creation of an Amazon Relational Database Service (RDS) Aurora database instance.", "false_positives": [ @@ -44,5 +45,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json index 7629ee4b821da..2b94ded55e7d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json @@ -34,7 +34,20 @@ "name": "Persistence", "reference": "https://attack.mitre.org/tactics/TA0003/" }, - "technique": [] + "technique": [ + { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "reference": "https://attack.mitre.org/techniques/T1547/", + "subtechnique": [ + { + "id": "T1547.001", + "name": "Registry Run Keys / Startup Folder", + "reference": "https://attack.mitre.org/techniques/T1547/001/" + } + ] + } + ] }, { "framework": "MITRE ATT&CK", @@ -54,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json new file mode 100644 index 0000000000000..54180a3a59a54 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic", + "Austin Songer" + ], + "description": "Identifies when an AWS Route Table has been modified or deleted.", + "false_positives": [ + "Route Table could be modified or deleted by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Route Table being modified from unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule. Also automated processes that uses Terraform may lead to false positives." + ], + "from": "now-60m", + "index": [ + "filebeat-*", + "logs-aws*" + ], + "interval": "10m", + "language": "kuery", + "license": "Elastic License v2", + "name": "AWS Route Table Modified or Deleted", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:aws.cloudtrail and event.provider:cloudtrail.amazonaws.com and event.action:(ReplaceRoute or ReplaceRouteTableAssociation or\nDeleteRouteTable or DeleteRoute or DisassociateRouteTable) and event.outcome:success\n", + "references": [ + "https://github.com/easttimor/aws-incident-response#network-routing", + "https://docs.datadoghq.com/security_platform/default_rules/cloudtrail-aws-route-table-modified", + "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ReplaceRoute.html", + "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ReplaceRouteTableAssociation", + "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteRouteTable.html", + "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteRoute.html", + "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DisassociateRouteTable.html" + ], + "risk_score": 21, + "rule_id": "e7cd5982-17c8-4959-874c-633acde7d426", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Network Security" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json new file mode 100644 index 0000000000000..544049d2c2df1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies when a child process is spawned by the screensaver engine process, which is consistent with an attacker's malicious payload being executed after the screensaver activated on the endpoint. An adversary can maintain persistence on a macOS endpoint by creating a malicious screensaver (.saver) file and configuring the screensaver plist file to execute code each time the screensaver is activated.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Unexpected Child Process of macOS Screensaver Engine", + "note": "## Triage and analysis\n\n- Analyze the descendant processes of the ScreenSaverEngine process for malicious code and suspicious behavior such\nas downloading a payload from a server\n- Review the installed and activated screensaver on the host. Triage the screensaver (.saver) file that was triggered to\nidentify whether the file is malicious or not.\n", + "query": "process where event.type == \"start\" and process.parent.name == \"ScreenSaverEngine\"\n", + "references": [ + "https://posts.specterops.io/saving-your-access-d562bf5bf90b", + "https://github.com/D00MFist/PersistentJXA" + ], + "risk_score": 47, + "rule_id": "48d7f54d-c29e-4430-93a9-9db6b5892270", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_plist_file_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_plist_file_modification.json new file mode 100644 index 0000000000000..dcd7427d7cbcd --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_plist_file_modification.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies when a screensaver plist file is modified by an unexpected process. An adversary can maintain persistence on a macOS endpoint by creating a malicious screensaver (.saver) file and configuring the screensaver plist file to execute code each time the screensaver is activated.", + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Screensaver Plist File Modified by Unexpected Process", + "note": "## Triage and analysis\n\n- Analyze the plist file modification event to identify whether the change was expected or not\n- Investigate the process that modified the plist file for malicious code or other suspicious behavior\n- Identify if any suspicious or known malicious screensaver (.saver) files were recently written to or modified on the host", + "query": "file where event.type != \"deletion\" and\n file.name: \"com.apple.screensaver.*.plist\" and\n file.path : (\n \"/Users/*/Library/Preferences/ByHost/*\",\n \"/Library/Managed Preferences/*\",\n \"/System/Library/Preferences/*\"\n ) and\n /* Filter OS processes modifying screensaver plist files */\n not process.executable : (\n \"/usr/sbin/cfprefsd\",\n \"/usr/libexec/xpcproxy\",\n \"/System/Library/CoreServices/ManagedClient.app/Contents/Resources/MCXCompositor\",\n \"/System/Library/CoreServices/ManagedClient.app/Contents/MacOS/ManagedClient\"\n )\n", + "references": [ + "https://posts.specterops.io/saving-your-access-d562bf5bf90b", + "https://github.com/D00MFist/PersistentJXA" + ], + "risk_score": 47, + "rule_id": "e6e8912f-283f-4d0d-8442-e0dcaf49944b", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "macOS", + "Threat Detection", + "Persistence" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "technique": [ + { + "id": "T1546", + "name": "Event Triggered Execution", + "reference": "https://attack.mitre.org/techniques/T1546/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json index ea5917a246afe..812c35350677f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json @@ -38,12 +38,19 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json index c63d96b106a01..1e55f014806f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json @@ -36,21 +36,14 @@ }, "technique": [ { - "id": "T1136", - "name": "Create Account", - "reference": "https://attack.mitre.org/techniques/T1136/", - "subtechnique": [ - { - "id": "T1136.001", - "name": "Local Account", - "reference": "https://attack.mitre.org/techniques/T1136/001/" - } - ] + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/" } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json index 0e2b01a1967d2..0777dfccab4bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json @@ -35,12 +35,19 @@ { "id": "T1136", "name": "Create Account", - "reference": "https://attack.mitre.org/techniques/T1136/" + "reference": "https://attack.mitre.org/techniques/T1136/", + "subtechnique": [ + { + "id": "T1136.001", + "name": "Local Account", + "reference": "https://attack.mitre.org/techniques/T1136/001/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 8 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json index dca20728b40fa..0d9cd0cb4020a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json @@ -38,12 +38,19 @@ { "id": "T1053", "name": "Scheduled Task/Job", - "reference": "https://attack.mitre.org/techniques/T1053/" + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.005", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/005/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json index fc3d94498d0cb..79e887a548bcb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json @@ -35,12 +35,19 @@ { "id": "T1546", "name": "Event Triggered Execution", - "reference": "https://attack.mitre.org/techniques/T1546/" + "reference": "https://attack.mitre.org/techniques/T1546/", + "subtechnique": [ + { + "id": "T1546.003", + "name": "Windows Management Instrumentation Event Subscription", + "reference": "https://attack.mitre.org/techniques/T1546/003/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json index 26248009f5a49..8da3be0b69d91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json @@ -4,7 +4,7 @@ ], "description": "Identifies suspicious commands executed via a web server, which may suggest a vulnerability and remote shell access.", "false_positives": [ - "Security audits, maintenance and network administrative scripts may trigger this alert when run under web processes." + "Security audits, maintenance, and network administrative scripts may trigger this alert when run under web processes." ], "from": "now-9m", "index": [ @@ -71,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json new file mode 100644 index 0000000000000..2a1231e96d8a5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies a new or modified federation domain, which can be used to create a trust between O365 and an external identity provider.", + "index": [ + "filebeat-*", + "logs-o365*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "New or Modified Federation Domain", + "note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:o365.audit and event.provider:Exchange and event.category:web and event.action:(\"Set-AcceptedDomain\" or \n\"Set-MsolDomainFederationSettings\" or \"Add-FederatedDomain\" or \"New-AcceptedDomain\" or \"Remove-AcceptedDomain\" or \"Remove-FederatedDomain\") and \nevent.outcome:success\n", + "references": [ + "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-accepteddomain?view=exchange-ps", + "https://docs.microsoft.com/en-us/powershell/module/exchange/remove-federateddomain?view=exchange-ps", + "https://docs.microsoft.com/en-us/powershell/module/exchange/new-accepteddomain?view=exchange-ps", + "https://docs.microsoft.com/en-us/powershell/module/exchange/add-federateddomain?view=exchange-ps", + "https://docs.microsoft.com/en-us/powershell/module/exchange/set-accepteddomain?view=exchange-ps", + "https://docs.microsoft.com/en-us/powershell/module/msonline/set-msoldomainfederationsettings?view=azureadps-1.0" + ], + "risk_score": 21, + "rule_id": "684554fc-0777-47ce-8c9b-3d01f198d7f8", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "Microsoft 365", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1484", + "name": "Domain Policy Modification", + "reference": "https://attack.mitre.org/techniques/T1484/", + "subtechnique": [ + { + "id": "T1484.002", + "name": "Domain Trust Modification", + "reference": "https://attack.mitre.org/techniques/T1484/002/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json new file mode 100644 index 0000000000000..c5e2669c1fade --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json @@ -0,0 +1,74 @@ +{ + "author": [ + "Austin Songer" + ], + "description": "Identifies the suspicious use of GetSessionToken. Tokens could be created and used by attackers to move laterally and escalate privileges.", + "false_positives": [ + "GetSessionToken may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. GetSessionToken from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + ], + "index": [ + "filebeat-*", + "logs-aws*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "AWS STS GetSessionToken Abuse", + "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "query": "event.dataset:aws.cloudtrail and event.provider:sts.amazonaws.com and event.action:GetSessionToken and \naws.cloudtrail.user_identity.type:IAMUser and event.outcome:success\n", + "references": [ + "https://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html" + ], + "risk_score": 21, + "rule_id": "b45ab1d2-712f-4f01-a751-df3826969807", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Identity and Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1548", + "name": "Abuse Elevation Control Mechanism", + "reference": "https://attack.mitre.org/techniques/T1548/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "technique": [ + { + "id": "T1550", + "name": "Use Alternate Authentication Material", + "reference": "https://attack.mitre.org/techniques/T1550/", + "subtechnique": [ + { + "id": "T1550.001", + "name": "Application Access Token", + "reference": "https://attack.mitre.org/techniques/T1550/001/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json index 9cdf474efb450..d9b7280392f38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json @@ -14,7 +14,7 @@ "name": "Unusual Parent-Child Relationship", "query": "process where event.type in (\"start\", \"process_started\") and\nprocess.parent.name != null and\n (\n /* suspicious parent processes */\n (process.name:\"autochk.exe\" and not process.parent.name:\"smss.exe\") or\n (process.name:(\"fontdrvhost.exe\", \"dwm.exe\") and not process.parent.name:(\"wininit.exe\", \"winlogon.exe\")) or\n (process.name:(\"consent.exe\", \"RuntimeBroker.exe\", \"TiWorker.exe\") and not process.parent.name:\"svchost.exe\") or\n (process.name:\"SearchIndexer.exe\" and not process.parent.name:\"services.exe\") or\n (process.name:\"SearchProtocolHost.exe\" and not process.parent.name:(\"SearchIndexer.exe\", \"dllhost.exe\")) or\n (process.name:\"dllhost.exe\" and not process.parent.name:(\"services.exe\", \"svchost.exe\")) or\n (process.name:\"smss.exe\" and not process.parent.name:(\"System\", \"smss.exe\")) or\n (process.name:\"csrss.exe\" and not process.parent.name:(\"smss.exe\", \"svchost.exe\")) or\n (process.name:\"wininit.exe\" and not process.parent.name:\"smss.exe\") or\n (process.name:\"winlogon.exe\" and not process.parent.name:\"smss.exe\") or\n (process.name:(\"lsass.exe\", \"LsaIso.exe\") and not process.parent.name:\"wininit.exe\") or\n (process.name:\"LogonUI.exe\" and not process.parent.name:(\"wininit.exe\", \"winlogon.exe\")) or\n (process.name:\"services.exe\" and not process.parent.name:\"wininit.exe\") or\n (process.name:\"svchost.exe\" and not process.parent.name:(\"MsMpEng.exe\", \"services.exe\")) or\n (process.name:\"spoolsv.exe\" and not process.parent.name:\"services.exe\") or\n (process.name:\"taskhost.exe\" and not process.parent.name:(\"services.exe\", \"svchost.exe\")) or\n (process.name:\"taskhostw.exe\" and not process.parent.name:(\"services.exe\", \"svchost.exe\")) or\n (process.name:\"userinit.exe\" and not process.parent.name:(\"dwm.exe\", \"winlogon.exe\")) or\n (process.name:(\"wmiprvse.exe\", \"wsmprovhost.exe\", \"winrshost.exe\") and not process.parent.name:\"svchost.exe\") or\n /* suspicious child processes */\n (process.parent.name:(\"SearchProtocolHost.exe\", \"taskhost.exe\", \"csrss.exe\") and not process.name:(\"werfault.exe\", \"wermgr.exe\", \"WerFaultSecure.exe\")) or\n (process.parent.name:\"autochk.exe\" and not process.name:(\"chkdsk.exe\", \"doskey.exe\", \"WerFault.exe\")) or\n (process.parent.name:\"smss.exe\" and not process.name:(\"autochk.exe\", \"smss.exe\", \"csrss.exe\", \"wininit.exe\", \"winlogon.exe\", \"setupcl.exe\", \"WerFault.exe\")) or\n (process.parent.name:\"wermgr.exe\" and not process.name:(\"WerFaultSecure.exe\", \"wermgr.exe\", \"WerFault.exe\")) or\n (process.parent.name:\"conhost.exe\" and not process.name:(\"mscorsvw.exe\", \"wermgr.exe\", \"WerFault.exe\", \"WerFaultSecure.exe\"))\n )\n", "references": [ - "https://github.com/sbousseaden/Slides/blob/master/Hunting MindMaps/PNG/Windows Processes%20TH.map.png", + "https://github.com/sbousseaden/Slides/blob/master/Hunting%20MindMaps/PNG/Windows%20Processes%20TH.map.png", "https://www.andreafortuna.org/2017/06/15/standard-windows-processes-a-brief-reference/" ], "risk_score": 47, @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 9 + "version": 10 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json index f582eba053d64..e4b1309c42644 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_module_match.json @@ -16,7 +16,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Threat Intel Filebeat Module Indicator Match", - "note": "## Triage and Analysis\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threatintel.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threatintel.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threatintel.indicator.matched.type` - this identifies the indicator type that matched the local observation\n", + "note": "## Triage and Analysis\n\n### Investigating Threat Intel Indicator Matches\n\nThreat Intel indicator match rules allow matching from a local observation such as an endpoint event that records a file\nhash with an entry of a file hash stored within the Threat Intel Filebeat module. Other examples of matches can occur on\nan IP address, registry path, URL and imphash.\n\nThe matches will be based on the incoming feed data so it's important to validate the data and review the results by\ninvestigating the associated activity to determine if it requires further investigation.\n\nIf an indicator matches a local observation, the following enriched fields will be generated to identify the indicator, field, and type matched.\n\n- `threatintel.indicator.matched.atomic` - this identifies the atomic indicator that matched the local observation\n- `threatintel.indicator.matched.field` - this identifies the indicator field that matched the local observation\n- `threatintel.indicator.matched.type` - this identifies the indicator type that matched the local observation\n\n#### Possible investigation steps:\n- Investigation should be validated and reviewed based on the data (file hash, registry path, URL, imphash) that was matched\nand viewing the source of that activity.\n- Consider the history of the indicator that was matched. Has it happened before? Is it happening on multiple machines?\nThese kinds of questions can help understand if the activity is related to legitimate behavior.\n- Consider the user and their role within the company, is this something related to their job or work function?\n\n### False Positive Analysis\n- For any matches found, it's important to consider the initial release date of that indicator. Threat intelligence can\nbe a great tool for augmenting existing security processes, while at the same time it should be understood that threat\nintelligence can represent a specific set of activity observed at a point in time. For example, an IP address\nmay have hosted malware observed in a Dridex campaign six months ago, but it's possible that IP has been remediated and\nno longer represents any threat.\n- Adversaries often use legitimate tools as network administrators such as `PsExec` or `AdFind`, these tools often find their\nway into indicator lists creating the potential for false positives.\n- It's possible after large and publicly written campaigns, curious employees might end up going directly to attacker infrastructure and generating these rules\n\n### Response and Remediation\n- If suspicious or malicious behavior is observed, immediate response should be taken to isolate activity to prevent further\npost-compromise behavior.\n- One example of a response if a machine matched a command and control IP address would be to add an entry to a network\ndevice such as a firewall or proxy appliance to prevent any outbound activity from leaving that machine.\n- Another example of a response with a malicious file hash match would involve validating if the file was properly quarantined,\nreview current running processes looking for any abnormal activity, and investigating for any other follow-up actions such as persistence or lateral movement\n", "query": "file.hash.*:* or file.pe.imphash:* or source.ip:* or destination.ip:* or url.full:* or registry.path:*\n", "references": [ "https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-threatintel.html" @@ -190,9 +190,9 @@ ] } ], - "threat_query": "event.module:threatintel and (threatintel.indicator.file.hash.*:* or threatintel.indicator.file.pe.imphash:* or threatintel.indicator.ip:* or threatintel.indicator.registry.path:* or threatintel.indicator.url.full:*)", + "threat_query": "@timestamp >= \"now-30d\" and event.module:threatintel and (threatintel.indicator.file.hash.*:* or threatintel.indicator.file.pe.imphash:* or threatintel.indicator.ip:* or threatintel.indicator.registry.path:* or threatintel.indicator.url.full:*)", "timeline_id": "495ad7a7-316e-4544-8a0f-9c098daee76e", "timeline_title": "Generic Threat Match Timeline", "type": "threat_match", - "version": 1 + "version": 2 } From b64604ac890f5bedd4ec68c27e84f32c3fa35510 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Thu, 14 Oct 2021 10:09:45 +0200 Subject: [PATCH 07/31] Implement hybrid approach to writing rule execution event logs (#114852) --- .../event_log_adapter/event_log_adapter.ts | 56 ++++++++++++------- .../rule_execution_log_client.ts | 2 +- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts index 6b1a0cd5b18d0..086cc12788a40 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_adapter.ts @@ -5,7 +5,10 @@ * 2.0. */ +import { sum } from 'lodash'; +import { SavedObjectsClientContract } from '../../../../../../../../src/core/server'; import { IEventLogService } from '../../../../../../event_log/server'; +import { SavedObjectsAdapter } from '../saved_objects_adapter/saved_objects_adapter'; import { FindBulkExecutionLogArgs, FindExecutionLogArgs, @@ -18,21 +21,32 @@ import { EventLogClient } from './event_log_client'; export class EventLogAdapter implements IRuleExecutionLogClient { private eventLogClient: EventLogClient; + /** + * @deprecated Saved objects adapter is used during the transition period while the event log doesn't support all features needed to implement the execution log. + * We use savedObjectsAdapter to write/read the latest rule execution status and eventLogClient to read/write historical execution data. + * We can remove savedObjectsAdapter as soon as the event log supports all methods that we need (find, findBulk). + */ + private savedObjectsAdapter: IRuleExecutionLogClient; - constructor(eventLogService: IEventLogService) { + constructor(eventLogService: IEventLogService, savedObjectsClient: SavedObjectsClientContract) { this.eventLogClient = new EventLogClient(eventLogService); + this.savedObjectsAdapter = new SavedObjectsAdapter(savedObjectsClient); } - public async find({ ruleId, logsCount = 1, spaceId }: FindExecutionLogArgs) { - return []; // TODO Implement + public async find(args: FindExecutionLogArgs) { + return this.savedObjectsAdapter.find(args); } - public async findBulk({ ruleIds, logsCount = 1, spaceId }: FindBulkExecutionLogArgs) { - return {}; // TODO Implement + public async findBulk(args: FindBulkExecutionLogArgs) { + return this.savedObjectsAdapter.findBulk(args); } - public async update({ attributes, spaceId, ruleName, ruleType }: UpdateExecutionLogArgs) { - // execution events are immutable, so we just log a status change istead of updating previous + public async update(args: UpdateExecutionLogArgs) { + const { attributes, spaceId, ruleName, ruleType } = args; + + await this.savedObjectsAdapter.update(args); + + // EventLog execution events are immutable, so we just log a status change istead of updating previous if (attributes.status) { this.eventLogClient.logStatusChange({ ruleName, @@ -45,16 +59,15 @@ export class EventLogAdapter implements IRuleExecutionLogClient { } public async delete(id: string) { - // execution events are immutable, nothing to do here + await this.savedObjectsAdapter.delete(id); + + // EventLog execution events are immutable, nothing to do here } - public async logExecutionMetrics({ - ruleId, - spaceId, - ruleType, - ruleName, - metrics, - }: LogExecutionMetricsArgs) { + public async logExecutionMetrics(args: LogExecutionMetricsArgs) { + const { ruleId, spaceId, ruleType, ruleName, metrics } = args; + await this.savedObjectsAdapter.logExecutionMetrics(args); + this.eventLogClient.logExecutionMetrics({ ruleId, ruleName, @@ -62,16 +75,19 @@ export class EventLogAdapter implements IRuleExecutionLogClient { spaceId, metrics: { executionGapDuration: metrics.executionGap?.asSeconds(), - totalIndexingDuration: metrics.indexingDurations?.reduce( - (acc, cur) => acc + Number(cur), - 0 - ), - totalSearchDuration: metrics.searchDurations?.reduce((acc, cur) => acc + Number(cur), 0), + totalIndexingDuration: metrics.indexingDurations + ? sum(metrics.indexingDurations.map(Number)) + : undefined, + totalSearchDuration: metrics.searchDurations + ? sum(metrics.searchDurations.map(Number)) + : undefined, }, }); } public async logStatusChange(args: LogStatusChangeArgs) { + await this.savedObjectsAdapter.logStatusChange(args); + if (args.metrics) { this.logExecutionMetrics({ ruleId: args.ruleId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts index 87a3b00cf4ed3..2d773fc35cce0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_execution_log_client.ts @@ -38,7 +38,7 @@ export class RuleExecutionLogClient implements IRuleExecutionLogClient { this.client = new SavedObjectsAdapter(savedObjectsClient); break; case UnderlyingLogClient.eventLog: - this.client = new EventLogAdapter(eventLogService); + this.client = new EventLogAdapter(eventLogService, savedObjectsClient); break; } } From 32f650f9f705cc2f466a1d39f2830e9d9a800807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Thu, 14 Oct 2021 10:48:36 +0200 Subject: [PATCH 08/31] [Stack monitoring] Fix logstash functional tests for react (#114819) * update logstash pipelines test subject * Add sorting to table options for pipelines table * fix sorting in logstash node pipelines table * remove commented code --- .../public/application/pages/logstash/node_pipelines.tsx | 4 +--- .../public/application/pages/logstash/pipelines.tsx | 8 +++----- x-pack/test/functional/apps/monitoring/index.js | 4 ---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx index e09850eaad5c9..740202da57d24 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/node_pipelines.tsx @@ -58,7 +58,6 @@ export const LogStashNodePipelinesPage: React.FC = ({ clusters } const getPageData = useCallback(async () => { const bounds = services.data?.query.timefilter.timefilter.getBounds(); - const options: any = getPaginationRouteOptions(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/logstash/node/${match.params.uuid}/pipelines`; const response = await services.http?.fetch(url, { method: 'POST', @@ -68,8 +67,7 @@ export const LogStashNodePipelinesPage: React.FC = ({ clusters } min: bounds.min.toISOString(), max: bounds.max.toISOString(), }, - pagination: options.pagination, - queryText: options.queryText, + ...getPaginationRouteOptions(), }), }); diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx index ac750ff81ddaa..c2dfe1c0dae7d 100644 --- a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx @@ -46,7 +46,6 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) => const bounds = services.data?.query.timefilter.timefilter.getBounds(); const url = `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipelines`; - const options: any = getPaginationRouteOptions(); const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ @@ -55,8 +54,7 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) => min: bounds.min.toISOString(), max: bounds.max.toISOString(), }, - pagination: options.pagination, - queryText: options.queryText, + ...getPaginationRouteOptions(), }), }); @@ -96,10 +94,10 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) => title={title} pageTitle={pageTitle} getPageData={getPageData} - data-test-subj="elasticsearchOverviewPage" + data-test-subj="logstashPipelinesListing" cluster={cluster} > -
{renderOverview(data)}
+
{renderOverview(data)}
); }; diff --git a/x-pack/test/functional/apps/monitoring/index.js b/x-pack/test/functional/apps/monitoring/index.js index 213007c7b71df..6a5b6ea813171 100644 --- a/x-pack/test/functional/apps/monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/index.js @@ -34,10 +34,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./kibana/instance')); loadTestFile(require.resolve('./kibana/instance_mb')); - // loadTestFile(require.resolve('./logstash/overview')); - // loadTestFile(require.resolve('./logstash/nodes')); - // loadTestFile(require.resolve('./logstash/node')); - loadTestFile(require.resolve('./logstash/pipelines')); loadTestFile(require.resolve('./logstash/pipelines_mb')); From f2f6bb52951f08227fb451d361f2d00b976852ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 14 Oct 2021 10:50:26 +0200 Subject: [PATCH 09/31] [Index Management] Added `data-test-subj` values to the index context menu buttons (#114900) --- .../__jest__/components/index_table.test.js | 53 ++++++++++--------- .../index_actions_context_menu.js | 19 +++++-- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index ba07db10d22d8..808c44ddecce0 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -88,7 +88,7 @@ const snapshot = (rendered) => { expect(rendered).toMatchSnapshot(); }; -const openMenuAndClickButton = (rendered, rowIndex, buttonIndex) => { +const openMenuAndClickButton = (rendered, rowIndex, buttonSelector) => { // Select a row. const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox'); checkboxes.at(rowIndex).simulate('change', { target: { checked: true } }); @@ -100,18 +100,18 @@ const openMenuAndClickButton = (rendered, rowIndex, buttonIndex) => { rendered.update(); // Click an action in the context menu. - const contextMenuButtons = findTestSubject(rendered, 'indexTableContextMenuButton'); - contextMenuButtons.at(buttonIndex).simulate('click'); + const contextMenuButton = findTestSubject(rendered, buttonSelector); + contextMenuButton.simulate('click'); rendered.update(); }; -const testEditor = (rendered, buttonIndex, rowIndex = 0) => { - openMenuAndClickButton(rendered, rowIndex, buttonIndex); +const testEditor = (rendered, buttonSelector, rowIndex = 0) => { + openMenuAndClickButton(rendered, rowIndex, buttonSelector); rendered.update(); snapshot(findTestSubject(rendered, 'detailPanelTabSelected').text()); }; -const testAction = (rendered, buttonIndex, rowIndex = 0) => { +const testAction = (rendered, buttonSelector, rowIndex = 0) => { // This is leaking some implementation details about how Redux works. Not sure exactly what's going on // but it looks like we're aware of how many Redux actions are dispatched in response to user interaction, // so we "time" our assertion based on how many Redux actions we observe. This is brittle because it @@ -127,7 +127,7 @@ const testAction = (rendered, buttonIndex, rowIndex = 0) => { dispatchedActionsCount++; }); - openMenuAndClickButton(rendered, rowIndex, buttonIndex); + openMenuAndClickButton(rendered, rowIndex, buttonSelector); // take snapshot of initial state. snapshot(status(rendered, rowIndex)); }; @@ -140,6 +140,11 @@ const namesText = (rendered) => { return names(rendered).map((button) => button.text()); }; +const getActionMenuButtons = (rendered) => { + return findTestSubject(rendered, 'indexContextMenu') + .find('button') + .map((span) => span.text()); +}; describe('index table', () => { beforeEach(() => { // Mock initialization of services @@ -232,7 +237,7 @@ describe('index table', () => { await runAllPromises(); rendered.update(); - let button = findTestSubject(rendered, 'indexTableContextMenuButton'); + let button = findTestSubject(rendered, 'indexActionsContextMenuButton'); expect(button.length).toEqual(0); const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox'); @@ -247,7 +252,7 @@ describe('index table', () => { await runAllPromises(); rendered.update(); - let button = findTestSubject(rendered, 'indexTableContextMenuButton'); + let button = findTestSubject(rendered, 'indexActionsContextMenuButton'); expect(button.length).toEqual(0); const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox'); @@ -353,7 +358,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); + snapshot(getActionMenuButtons(rendered)); }); test('should show the right context menu options when one index is selected and closed', async () => { @@ -367,7 +372,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); + snapshot(getActionMenuButtons(rendered)); }); test('should show the right context menu options when one open and one closed index is selected', async () => { @@ -382,7 +387,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); + snapshot(getActionMenuButtons(rendered)); }); test('should show the right context menu options when more than one open index is selected', async () => { @@ -397,7 +402,7 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); + snapshot(getActionMenuButtons(rendered)); }); test('should show the right context menu options when more than one closed index is selected', async () => { @@ -412,28 +417,28 @@ describe('index table', () => { const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton'); actionButton.simulate('click'); rendered.update(); - snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text())); + snapshot(getActionMenuButtons(rendered)); }); test('flush button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testAction(rendered, 8); + testAction(rendered, 'flushIndexMenuButton'); }); test('clear cache button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testAction(rendered, 7); + testAction(rendered, 'clearCacheIndexMenuButton'); }); test('refresh button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testAction(rendered, 6); + testAction(rendered, 'refreshIndexMenuButton'); }); test('force merge button works from context menu', async () => { @@ -442,7 +447,7 @@ describe('index table', () => { rendered.update(); const rowIndex = 0; - openMenuAndClickButton(rendered, rowIndex, 5); + openMenuAndClickButton(rendered, rowIndex, 'forcemergeIndexMenuButton'); snapshot(status(rendered, rowIndex)); expect(rendered.find('.euiModal').length).toBe(1); @@ -478,7 +483,7 @@ describe('index table', () => { JSON.stringify(modifiedIndices), ]); - testAction(rendered, 4); + testAction(rendered, 'closeIndexMenuButton'); }); test('open index button works from context menu', async () => { @@ -499,34 +504,34 @@ describe('index table', () => { JSON.stringify(modifiedIndices), ]); - testAction(rendered, 3, 1); + testAction(rendered, 'openIndexMenuButton', 1); }); test('show settings button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testEditor(rendered, 0); + testEditor(rendered, 'showSettingsIndexMenuButton'); }); test('show mappings button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testEditor(rendered, 1); + testEditor(rendered, 'showMappingsIndexMenuButton'); }); test('show stats button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testEditor(rendered, 2); + testEditor(rendered, 'showStatsIndexMenuButton'); }); test('edit index button works from context menu', async () => { const rendered = mountWithIntl(component); await runAllPromises(); rendered.update(); - testEditor(rendered, 3); + testEditor(rendered, 'editIndexMenuButton'); }); }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index a97d5b11161b1..c5bd62feff826 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -75,6 +75,7 @@ export class IndexActionsContextMenu extends Component { const items = []; if (!detailPanel && selectedIndexCount === 1) { items.push({ + 'data-test-subj': 'showSettingsIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.showIndexSettingsLabel', { defaultMessage: 'Show {selectedIndexCount, plural, one {index} other {indices} } settings', @@ -85,6 +86,7 @@ export class IndexActionsContextMenu extends Component { }, }); items.push({ + 'data-test-subj': 'showMappingsIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.showIndexMappingLabel', { defaultMessage: 'Show {selectedIndexCount, plural, one {index} other {indices} } mapping', values: { selectedIndexCount }, @@ -95,6 +97,7 @@ export class IndexActionsContextMenu extends Component { }); if (allOpen) { items.push({ + 'data-test-subj': 'showStatsIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.showIndexStatsLabel', { defaultMessage: 'Show {selectedIndexCount, plural, one {index} other {indices} } stats', values: { selectedIndexCount }, @@ -105,6 +108,7 @@ export class IndexActionsContextMenu extends Component { }); } items.push({ + 'data-test-subj': 'editIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.editIndexSettingsLabel', { defaultMessage: 'Edit {selectedIndexCount, plural, one {index} other {indices} } settings', @@ -117,6 +121,7 @@ export class IndexActionsContextMenu extends Component { } if (allOpen) { items.push({ + 'data-test-subj': 'closeIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.closeIndexLabel', { defaultMessage: 'Close {selectedIndexCount, plural, one {index} other {indices} }', values: { selectedIndexCount }, @@ -131,6 +136,7 @@ export class IndexActionsContextMenu extends Component { }, }); items.push({ + 'data-test-subj': 'forcemergeIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.forceMergeIndexLabel', { defaultMessage: 'Force merge {selectedIndexCount, plural, one {index} other {indices} }', values: { selectedIndexCount }, @@ -141,6 +147,7 @@ export class IndexActionsContextMenu extends Component { }, }); items.push({ + 'data-test-subj': 'refreshIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.refreshIndexLabel', { defaultMessage: 'Refresh {selectedIndexCount, plural, one {index} other {indices} }', values: { selectedIndexCount }, @@ -150,6 +157,7 @@ export class IndexActionsContextMenu extends Component { }, }); items.push({ + 'data-test-subj': 'clearCacheIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.clearIndexCacheLabel', { defaultMessage: 'Clear {selectedIndexCount, plural, one {index} other {indices} } cache', values: { selectedIndexCount }, @@ -159,6 +167,7 @@ export class IndexActionsContextMenu extends Component { }, }); items.push({ + 'data-test-subj': 'flushIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.flushIndexLabel', { defaultMessage: 'Flush {selectedIndexCount, plural, one {index} other {indices} }', values: { selectedIndexCount }, @@ -191,6 +200,7 @@ export class IndexActionsContextMenu extends Component { } } else { items.push({ + 'data-test-subj': 'openIndexMenuButton', name: i18n.translate('xpack.idxMgmt.indexActionsMenu.openIndexLabel', { defaultMessage: 'Open {selectedIndexCount, plural, one {index} other {indices} }', values: { selectedIndexCount }, @@ -239,9 +249,6 @@ export class IndexActionsContextMenu extends Component { } } }); - items.forEach((item) => { - item['data-test-subj'] = 'indexTableContextMenuButton'; - }); const panelTree = { id: 0, title: i18n.translate('xpack.idxMgmt.indexActionsMenu.panelTitle', { @@ -732,7 +739,11 @@ export class IndexActionsContextMenu extends Component { anchorPosition={anchorPosition} repositionOnScroll > - + ); From b0daf935cf01ffe4377063a115648a2fdfbe081d Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 14 Oct 2021 11:07:26 +0200 Subject: [PATCH 10/31] [ML] APM Correlations: Round duration values to be used in range aggregations. (#114833) A change in the ES range agg no longer accepts numbers with decimals if the underlying field is typed as long. This fixes the issue by rounding the values we pass on to the range agg. --- .../queries/query_histogram_range_steps.test.ts | 4 ++-- .../search_strategies/queries/query_histogram_range_steps.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts index 25ce39cbcf17b..ffc86c7ef6c32 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts @@ -106,8 +106,8 @@ describe('query_histogram_range_steps', () => { ); expect(resp.length).toEqual(100); - expect(resp[0]).toEqual(9.260965422132594); - expect(resp[99]).toEqual(18521.930844265193); + expect(resp[0]).toEqual(9); + expect(resp[99]).toEqual(18522); expect(esClientSearchMock).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts index 973787833577c..790919d193028 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts @@ -19,10 +19,11 @@ import { getRequestBase } from './get_request_base'; const getHistogramRangeSteps = (min: number, max: number, steps: number) => { // A d3 based scale function as a helper to get equally distributed bins on a log scale. + // We round the final values because the ES range agg we use won't accept numbers with decimals for `transaction.duration.us`. const logFn = scaleLog().domain([min, max]).range([1, steps]); return [...Array(steps).keys()] .map(logFn.invert) - .map((d) => (isNaN(d) ? 0 : d)); + .map((d) => (isNaN(d) ? 0 : Math.round(d))); }; export const getHistogramIntervalRequest = ( From 1d3c8b7dc70f2ba260cca330fdad0b973ffbccb9 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Thu, 14 Oct 2021 11:26:49 +0200 Subject: [PATCH 11/31] [Security Solution] Edit host isolation exception IP UI (#114279) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pages/event_filters/store/middleware.ts | 2 +- .../host_isolation_exceptions/service.ts | 24 +++ .../host_isolation_exceptions/store/action.ts | 21 ++- .../store/middleware.test.ts | 107 +++++++++++- .../store/middleware.ts | 88 +++++++++- .../store/reducer.ts | 9 + .../store/selector.ts | 16 ++ .../pages/host_isolation_exceptions/types.ts | 5 +- .../view/components/delete_modal.test.tsx | 2 +- .../view/components/form.test.tsx | 53 +++++- .../view/components/form.tsx | 16 +- .../view/components/form_flyout.test.tsx | 117 ++++++++++++- .../view/components/form_flyout.tsx | 163 +++++++++++++----- .../view/components/translations.ts | 65 +++++++ .../view/host_isolation_exceptions_list.tsx | 22 ++- .../pages/trusted_apps/store/middleware.ts | 2 +- 16 files changed, 633 insertions(+), 79 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts index 0c90e21b49530..c77494aad2de2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts @@ -231,8 +231,8 @@ const refreshListDataIfNeeded: MiddlewareActionHandler = async (store, eventFilt dispatch({ type: 'eventFiltersListPageDataChanged', payload: { - type: 'LoadingResourceState', // @ts-expect-error-next-line will be fixed with when AsyncResourceState is refactored (#830) + type: 'LoadingResourceState', previousState: getCurrentListPageDataState(state), }, }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts index 8af353a3c9531..b58c2d901c2cc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/service.ts @@ -9,6 +9,7 @@ import { CreateExceptionListItemSchema, ExceptionListItemSchema, FoundExceptionListItemSchema, + UpdateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { HttpStart } from 'kibana/public'; @@ -88,3 +89,26 @@ export async function deleteHostIsolationExceptionItems(http: HttpStart, id: str }, }); } + +export async function getOneHostIsolationExceptionItem( + http: HttpStart, + id: string +): Promise { + await ensureHostIsolationExceptionsListExists(http); + return http.get(EXCEPTION_LIST_ITEM_URL, { + query: { + id, + namespace_type: 'agnostic', + }, + }); +} + +export async function updateOneHostIsolationExceptionItem( + http: HttpStart, + exception: UpdateExceptionListItemSchema +): Promise { + await ensureHostIsolationExceptionsListExists(http); + return http.put(EXCEPTION_LIST_ITEM_URL, { + body: JSON.stringify(exception), + }); +} diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts index a5fae36486f98..237868ad18c50 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/action.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { + ExceptionListItemSchema, + UpdateExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; import { Action } from 'redux'; import { HostIsolationExceptionsPageState } from '../types'; @@ -38,10 +41,24 @@ export type HostIsolationExceptionsDeleteStatusChanged = Action<'hostIsolationExceptionsDeleteStatusChanged'> & { payload: HostIsolationExceptionsPageState['deletion']['status']; }; + +export type HostIsolationExceptionsMarkToEdit = Action<'hostIsolationExceptionsMarkToEdit'> & { + payload: { + id: string; + }; +}; + +export type HostIsolationExceptionsSubmitEdit = Action<'hostIsolationExceptionsSubmitEdit'> & { + payload: UpdateExceptionListItemSchema; +}; + export type HostIsolationExceptionsPageAction = | HostIsolationExceptionsPageDataChanged | HostIsolationExceptionsCreateEntry | HostIsolationExceptionsFormStateChanged | HostIsolationExceptionsDeleteItem | HostIsolationExceptionsSubmitDelete - | HostIsolationExceptionsDeleteStatusChanged; + | HostIsolationExceptionsDeleteStatusChanged + | HostIsolationExceptionsFormEntryChanged + | HostIsolationExceptionsMarkToEdit + | HostIsolationExceptionsSubmitEdit; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts index 266853fdab5e2..878c17a1a2757 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.test.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { + CreateExceptionListItemSchema, + UpdateEndpointListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; import { applyMiddleware, createStore, Store } from 'redux'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; @@ -24,6 +27,8 @@ import { createHostIsolationExceptionItem, deleteHostIsolationExceptionItems, getHostIsolationExceptionItems, + getOneHostIsolationExceptionItem, + updateOneHostIsolationExceptionItem, } from '../service'; import { HostIsolationExceptionsPageState } from '../types'; import { createEmptyHostIsolationException } from '../utils'; @@ -36,6 +41,8 @@ jest.mock('../service'); const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; const deleteHostIsolationExceptionItemsMock = deleteHostIsolationExceptionItems as jest.Mock; const createHostIsolationExceptionItemMock = createHostIsolationExceptionItem as jest.Mock; +const getOneHostIsolationExceptionItemMock = getOneHostIsolationExceptionItem as jest.Mock; +const updateOneHostIsolationExceptionItemMock = updateOneHostIsolationExceptionItem as jest.Mock; const fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); @@ -170,6 +177,7 @@ describe('Host isolation exceptions middleware', () => { ], }; }); + it('should dispatch a form loading state when an entry is submited', async () => { const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { validate({ payload }) { @@ -182,6 +190,7 @@ describe('Host isolation exceptions middleware', () => { }); await waiter; }); + it('should dispatch a form success state when an entry is confirmed by the API', async () => { const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { validate({ payload }) { @@ -198,6 +207,7 @@ describe('Host isolation exceptions middleware', () => { exception: entry, }); }); + it('should dispatch a form failure state when an entry is rejected by the API', async () => { createHostIsolationExceptionItemMock.mockRejectedValue({ body: { message: 'error message', statusCode: 500, error: 'Not today' }, @@ -215,6 +225,101 @@ describe('Host isolation exceptions middleware', () => { }); }); + describe('When updating an item from host isolation exceptions', () => { + const fakeId = 'dc5d1d00-2766-11ec-981f-7f84cfc8764f'; + let fakeException: UpdateEndpointListItemSchema; + beforeEach(() => { + fakeException = { + ...createEmptyHostIsolationException(), + name: 'name edit me', + description: 'initial description', + id: fakeId, + item_id: fakeId, + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '10.0.0.5', + }, + ], + }; + getOneHostIsolationExceptionItemMock.mockReset(); + getOneHostIsolationExceptionItemMock.mockImplementation(async () => { + return fakeException; + }); + }); + + it('should load data from an entry when an exception is marked to edit', async () => { + const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormEntryChanged'); + store.dispatch({ + type: 'hostIsolationExceptionsMarkToEdit', + payload: { + id: fakeId, + }, + }); + await waiter; + expect(getOneHostIsolationExceptionItemMock).toHaveBeenCalledWith(fakeCoreStart.http, fakeId); + }); + + it('should call the update API when an item edit is submitted', async () => { + const waiter = Promise.all([ + // loading status + spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { + validate: ({ payload }) => { + return isLoadingResourceState(payload); + }, + }), + // loaded status + spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { + validate({ payload }) { + return isLoadedResourceState(payload); + }, + }), + ]); + store.dispatch({ + type: 'hostIsolationExceptionsSubmitEdit', + payload: fakeException, + }); + expect(updateOneHostIsolationExceptionItemMock).toHaveBeenCalledWith(fakeCoreStart.http, { + name: 'name edit me', + description: 'initial description', + id: fakeId, + item_id: fakeId, + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '10.0.0.5', + }, + ], + namespace_type: 'agnostic', + os_types: ['windows', 'linux', 'macos'], + tags: ['policy:all'], + type: 'simple', + comments: [], + }); + await waiter; + }); + + it('should dispatch a form failure state when an entry is rejected by the API', async () => { + updateOneHostIsolationExceptionItemMock.mockRejectedValue({ + body: { message: 'error message', statusCode: 500, error: 'Not today' }, + }); + const waiter = spyMiddleware.waitForAction('hostIsolationExceptionsFormStateChanged', { + validate({ payload }) { + return isFailedResourceState(payload); + }, + }); + store.dispatch({ + type: 'hostIsolationExceptionsSubmitEdit', + payload: fakeException, + }); + await waiter; + }); + }); + describe('When deleting an item from host isolation exceptions', () => { beforeEach(() => { deleteHostIsolationExceptionItemsMock.mockReset(); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts index bbc754e8155b0..2587fff5bfafd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/middleware.ts @@ -9,11 +9,12 @@ import { CreateExceptionListItemSchema, ExceptionListItemSchema, FoundExceptionListItemSchema, + UpdateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { CoreStart, HttpSetup, HttpStart } from 'kibana/public'; import { matchPath } from 'react-router-dom'; -import { transformNewItemOutput } from '@kbn/securitysolution-list-hooks'; -import { AppLocation, Immutable } from '../../../../../common/endpoint/types'; +import { transformNewItemOutput, transformOutput } from '@kbn/securitysolution-list-hooks'; +import { AppLocation, Immutable, ImmutableObject } from '../../../../../common/endpoint/types'; import { ImmutableMiddleware, ImmutableMiddlewareAPI } from '../../../../common/store'; import { AppAction } from '../../../../common/store/actions'; import { MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../common/constants'; @@ -21,11 +22,14 @@ import { parseQueryFilterToKQL } from '../../../common/utils'; import { createFailedResourceState, createLoadedResourceState, + createLoadingResourceState, } from '../../../state/async_resource_builders'; import { deleteHostIsolationExceptionItems, getHostIsolationExceptionItems, createHostIsolationExceptionItem, + getOneHostIsolationExceptionItem, + updateOneHostIsolationExceptionItem, } from '../service'; import { HostIsolationExceptionsPageState } from '../types'; import { getCurrentListPageDataState, getCurrentLocation, getItemToDelete } from './selector'; @@ -53,6 +57,14 @@ export const createHostIsolationExceptionsPageMiddleware = ( if (action.type === 'hostIsolationExceptionsSubmitDelete') { deleteHostIsolationExceptionsItem(store, coreStart.http); } + + if (action.type === 'hostIsolationExceptionsMarkToEdit') { + loadHostIsolationExceptionsItem(store, coreStart.http, action.payload.id); + } + + if (action.type === 'hostIsolationExceptionsSubmitEdit') { + updateHostIsolationExceptionsItem(store, coreStart.http, action.payload); + } }; }; @@ -67,8 +79,8 @@ async function createHostIsolationException( dispatch({ type: 'hostIsolationExceptionsFormStateChanged', payload: { - type: 'LoadingResourceState', // @ts-expect-error-next-line will be fixed with when AsyncResourceState is refactored (#830) + type: 'LoadingResourceState', previousState: entry, }, }); @@ -110,8 +122,8 @@ async function loadHostIsolationExceptionsList( dispatch({ type: 'hostIsolationExceptionsPageDataChanged', payload: { - type: 'LoadingResourceState', // @ts-expect-error-next-line will be fixed with when AsyncResourceState is refactored (#830) + type: 'LoadingResourceState', previousState: getCurrentListPageDataState(store.getState()), }, }); @@ -152,8 +164,8 @@ async function deleteHostIsolationExceptionsItem( dispatch({ type: 'hostIsolationExceptionsDeleteStatusChanged', payload: { - type: 'LoadingResourceState', // @ts-expect-error-next-line will be fixed with when AsyncResourceState is refactored (#830) + type: 'LoadingResourceState', previousState: store.getState().deletion.status, }, }); @@ -172,3 +184,69 @@ async function deleteHostIsolationExceptionsItem( }); } } + +async function loadHostIsolationExceptionsItem( + store: ImmutableMiddlewareAPI, + http: HttpSetup, + id: string +) { + const { dispatch } = store; + try { + const exception: UpdateExceptionListItemSchema = await getOneHostIsolationExceptionItem( + http, + id + ); + dispatch({ + type: 'hostIsolationExceptionsFormEntryChanged', + payload: exception, + }); + } catch (error) { + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: createFailedResourceState(error.body ?? error), + }); + } +} +async function updateHostIsolationExceptionsItem( + store: ImmutableMiddlewareAPI, + http: HttpSetup, + exception: ImmutableObject +) { + const { dispatch } = store; + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: createLoadingResourceState(createLoadedResourceState(exception)), + }); + + try { + const entry = transformOutput(exception as UpdateExceptionListItemSchema); + // Clean unnecessary fields for update action + const fieldsToRemove: Array = [ + 'created_at', + 'created_by', + 'created_at', + 'created_by', + 'list_id', + 'tie_breaker_id', + 'updated_at', + 'updated_by', + ]; + + fieldsToRemove.forEach((field) => { + delete entry[field as keyof UpdateExceptionListItemSchema]; + }); + const response: ExceptionListItemSchema = await updateOneHostIsolationExceptionItem( + http, + entry + ); + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: createLoadedResourceState(response), + }); + } catch (error) { + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: createFailedResourceState(error.body ?? error), + }); + } +} diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts index d97295598f445..77a1c248d0cf0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/reducer.ts @@ -56,6 +56,15 @@ export const hostIsolationExceptionsPageReducer: StateReducer = ( }, }; } + case 'hostIsolationExceptionsFormEntryChanged': { + return { + ...state, + form: { + ...state.form, + entry: action.payload, + }, + }; + } case 'hostIsolationExceptionsPageDataChanged': { return { ...state, diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts index 4462864e90702..3eca524d830d5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/store/selector.ts @@ -9,6 +9,7 @@ import { Pagination } from '@elastic/eui'; import { ExceptionListItemSchema, FoundExceptionListItemSchema, + UpdateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/endpoint/types'; @@ -108,3 +109,18 @@ export const getDeleteError: HostIsolationExceptionsSelector = (state) => { + return state.form; +}; + +export const getFormStatusFailure: HostIsolationExceptionsSelector = + createSelector(getFormState, (form) => { + if (isFailedResourceState(form.status)) { + return form.status.error; + } + }); + +export const getExceptionToEdit: HostIsolationExceptionsSelector< + UpdateExceptionListItemSchema | undefined +> = (state) => (state.form.entry ? (state.form.entry as UpdateExceptionListItemSchema) : undefined); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts index 1a74042fb652e..2e61a39eb7542 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/types.ts @@ -9,6 +9,7 @@ import type { CreateExceptionListItemSchema, ExceptionListItemSchema, FoundExceptionListItemSchema, + UpdateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { AsyncResourceState } from '../../state/async_resource_state'; @@ -29,7 +30,7 @@ export interface HostIsolationExceptionsPageState { status: AsyncResourceState; }; form: { - entry?: CreateExceptionListItemSchema; - status: AsyncResourceState; + entry?: CreateExceptionListItemSchema | UpdateExceptionListItemSchema; + status: AsyncResourceState; }; } diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx index 0b09b4bfa14c4..2a75ab0622128 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/delete_modal.test.tsx @@ -83,7 +83,7 @@ describe('When on the host isolation exceptions delete modal', () => { act(() => { fireEvent.click(cancelButton); }); - await waiter; + expect(await waiter).toBeTruthy(); }); it('should show success toast after the delete is completed', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx index b06449de69d8c..826f7bf6c4d8a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx @@ -12,12 +12,16 @@ import { AppContextTestRender, createAppRootMockRenderer, } from '../../../../../common/mock/endpoint'; -import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { + CreateExceptionListItemSchema, + UpdateExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; import userEvent from '@testing-library/user-event'; +import uuid from 'uuid'; describe('When on the host isolation exceptions add entry form', () => { let render: ( - exception: CreateExceptionListItemSchema + exception: CreateExceptionListItemSchema | UpdateExceptionListItemSchema ) => ReturnType; let renderResult: ReturnType; const onChange = jest.fn(); @@ -27,7 +31,7 @@ describe('When on the host isolation exceptions add entry form', () => { onChange.mockReset(); onError.mockReset(); const mockedContext = createAppRootMockRenderer(); - render = (exception: CreateExceptionListItemSchema) => { + render = (exception) => { return mockedContext.render( ); @@ -72,4 +76,47 @@ describe('When on the host isolation exceptions add entry form', () => { }); }); }); + describe('When editing an existing exception', () => { + let existingException: UpdateExceptionListItemSchema; + beforeEach(() => { + existingException = { + ...createEmptyHostIsolationException(), + name: 'name edit me', + description: 'initial description', + id: uuid.v4(), + item_id: uuid.v4(), + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '10.0.0.1', + }, + ], + }; + renderResult = render(existingException); + }); + it('should render the form with pre-filled inputs', () => { + expect(renderResult.getByTestId('hostIsolationExceptions-form-name-input')).toHaveValue( + 'name edit me' + ); + expect(renderResult.getByTestId('hostIsolationExceptions-form-ip-input')).toHaveValue( + '10.0.0.1' + ); + expect( + renderResult.getByTestId('hostIsolationExceptions-form-description-input') + ).toHaveValue('initial description'); + }); + it('should call onChange when a value is introduced in a field', () => { + const ipInput = renderResult.getByTestId('hostIsolationExceptions-form-ip-input'); + userEvent.clear(ipInput); + userEvent.type(ipInput, '10.0.100.1'); + expect(onChange).toHaveBeenCalledWith({ + ...existingException, + entries: [ + { field: 'destination.ip', operator: 'included', type: 'match', value: '10.0.100.1' }, + ], + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx index 84263f9d07c81..7b13df16da483 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx @@ -16,7 +16,10 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { + CreateExceptionListItemSchema, + UpdateExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { isValidIPv4OrCIDR } from '../../utils'; import { @@ -34,18 +37,19 @@ interface ExceptionIpEntry { field: 'destination.ip'; operator: 'included'; type: 'match'; - value: ''; + value: string; } export const HostIsolationExceptionsForm: React.FC<{ - exception: CreateExceptionListItemSchema; + exception: CreateExceptionListItemSchema | UpdateExceptionListItemSchema; onError: (error: boolean) => void; - onChange: (exception: CreateExceptionListItemSchema) => void; + onChange: (exception: CreateExceptionListItemSchema | UpdateExceptionListItemSchema) => void; }> = memo(({ exception, onError, onChange }) => { + const ipEntry = exception.entries[0] as ExceptionIpEntry; const [hasBeenInputNameVisited, setHasBeenInputNameVisited] = useState(false); const [hasBeenInputIpVisited, setHasBeenInputIpVisited] = useState(false); - const [hasNameError, setHasNameError] = useState(true); - const [hasIpError, setHasIpError] = useState(true); + const [hasNameError, setHasNameError] = useState(!exception.name); + const [hasIpError, setHasIpError] = useState(!ipEntry.value); useEffect(() => { onError(hasNameError || hasIpError); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx index 6cfc9f56beadf..4ab4ed785e491 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.test.tsx @@ -14,6 +14,8 @@ import userEvent from '@testing-library/user-event'; import { HostIsolationExceptionsFormFlyout } from './form_flyout'; import { act } from 'react-dom/test-utils'; import { HOST_ISOLATION_EXCEPTIONS_PATH } from '../../../../../../common/constants'; +import uuid from 'uuid'; +import { createEmptyHostIsolationException } from '../../utils'; jest.mock('../../service.ts'); @@ -23,8 +25,6 @@ describe('When on the host isolation exceptions flyout form', () => { let renderResult: ReturnType; let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; - // const createHostIsolationExceptionItemMock = createHostIsolationExceptionItem as jest.mock; - beforeEach(() => { mockedContext = createAppRootMockRenderer(); render = () => { @@ -34,7 +34,11 @@ describe('When on the host isolation exceptions flyout form', () => { }); describe('When creating a new exception', () => { - describe('with invalid data', () => { + beforeEach(() => { + mockedContext.history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=create`); + }); + + describe('with invalida data', () => { it('should show disabled buttons when the form first load', () => { renderResult = render(); expect(renderResult.getByTestId('add-exception-cancel-button')).not.toHaveAttribute( @@ -45,6 +49,7 @@ describe('When on the host isolation exceptions flyout form', () => { ); }); }); + describe('with valid data', () => { beforeEach(() => { renderResult = render(); @@ -53,6 +58,7 @@ describe('When on the host isolation exceptions flyout form', () => { userEvent.type(nameInput, 'test name'); userEvent.type(ipInput, '10.0.0.1'); }); + it('should show enable buttons when the form is valid', () => { expect(renderResult.getByTestId('add-exception-cancel-button')).not.toHaveAttribute( 'disabled' @@ -61,13 +67,15 @@ describe('When on the host isolation exceptions flyout form', () => { 'disabled' ); }); + it('should submit the entry data when submit is pressed with valid data', async () => { const confirmButton = renderResult.getByTestId('add-exception-confirm-button'); expect(confirmButton).not.toHaveAttribute('disabled'); const waiter = waitForAction('hostIsolationExceptionsCreateEntry'); userEvent.click(confirmButton); - await waiter; + expect(await waiter).toBeTruthy(); }); + it('should disable the submit button when an operation is in progress', () => { act(() => { mockedContext.store.dispatch({ @@ -81,6 +89,7 @@ describe('When on the host isolation exceptions flyout form', () => { const confirmButton = renderResult.getByTestId('add-exception-confirm-button'); expect(confirmButton).toHaveAttribute('disabled'); }); + it('should show a toast and close the flyout when the operation is finished', () => { mockedContext.history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=create`); act(() => { @@ -95,13 +104,14 @@ describe('When on the host isolation exceptions flyout form', () => { expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalled(); expect(mockedContext.history.location.search).toBe(''); }); - it('should show an error toast operation fails and enable the submit button', () => { + + it('should show an error toast if operation fails and enable the submit button', async () => { act(() => { mockedContext.store.dispatch({ type: 'hostIsolationExceptionsFormStateChanged', payload: { type: 'FailedResourceState', - previousState: { type: 'UninitialisedResourceState' }, + error: new Error('mocked error'), }, }); }); @@ -111,4 +121,99 @@ describe('When on the host isolation exceptions flyout form', () => { }); }); }); + describe('When editing an existing exception', () => { + const fakeId = 'dc5d1d00-2766-11ec-981f-7f84cfc8764f'; + beforeEach(() => { + mockedContext.history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=edit&id=${fakeId}`); + }); + + describe('without loaded data', () => { + it('should show a loading status while the item is loaded', () => { + renderResult = render(); + expect(renderResult.getByTestId('loading-spinner')).toBeTruthy(); + }); + + it('should request to load data about the editing exception', async () => { + const waiter = waitForAction('hostIsolationExceptionsMarkToEdit', { + validate: ({ payload }) => { + return payload.id === fakeId; + }, + }); + renderResult = render(); + expect(await waiter).toBeTruthy(); + }); + + it('should show a warning toast if the item fails to load', () => { + renderResult = render(); + act(() => { + mockedContext.store.dispatch({ + type: 'hostIsolationExceptionsFormEntryChanged', + payload: undefined, + }); + + mockedContext.store.dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'FailedResourceState', + error: new Error('mocked error'), + }, + }); + }); + expect(mockedContext.coreStart.notifications.toasts.addWarning).toHaveBeenCalled(); + }); + }); + + describe('with loaded data', () => { + beforeEach(async () => { + mockedContext.store.dispatch({ + type: 'hostIsolationExceptionsFormEntryChanged', + payload: { + ...createEmptyHostIsolationException(), + name: 'name edit me', + description: 'initial description', + id: fakeId, + item_id: uuid.v4(), + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '10.0.0.5', + }, + ], + }, + }); + renderResult = render(); + }); + + it('should request data again if the url id is changed', async () => { + const otherId = 'd75fbd74-2a92-11ec-8d3d-0242ac130003'; + act(() => { + mockedContext.history.push(`${HOST_ISOLATION_EXCEPTIONS_PATH}?show=edit&id=${otherId}`); + }); + await waitForAction('hostIsolationExceptionsMarkToEdit', { + validate: ({ payload }) => { + return payload.id === otherId; + }, + }); + }); + + it('should enable the buttons from the start', () => { + expect(renderResult.getByTestId('add-exception-cancel-button')).not.toHaveAttribute( + 'disabled' + ); + expect(renderResult.getByTestId('add-exception-confirm-button')).not.toHaveAttribute( + 'disabled' + ); + }); + + it('should submit the entry data when submit is pressed with valid data', async () => { + const confirmButton = renderResult.getByTestId('add-exception-confirm-button'); + expect(confirmButton).not.toHaveAttribute('disabled'); + const waiter = waitForAction('hostIsolationExceptionsSubmitEdit'); + userEvent.click(confirmButton); + expect(await waiter).toBeTruthy(); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx index 5502a1b8ea2b1..de12616c67a3c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form_flyout.tsx @@ -16,20 +16,32 @@ import { EuiFlyoutHeader, EuiTitle, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { + CreateExceptionListItemSchema, + UpdateExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import { omit } from 'lodash'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { Dispatch } from 'redux'; import { Loader } from '../../../../../common/components/loader'; import { useToasts } from '../../../../../common/lib/kibana'; +import { getHostIsolationExceptionsListPath } from '../../../../common/routing'; import { - isFailedResourceState, isLoadedResourceState, isLoadingResourceState, } from '../../../../state/async_resource_state'; +import { + getCreateErrorMessage, + getCreationSuccessMessage, + getLoadErrorMessage, + getUpdateErrorMessage, + getUpdateSuccessMessage, +} from './translations'; import { HostIsolationExceptionsPageAction } from '../../store/action'; +import { getCurrentLocation, getExceptionToEdit, getFormStatusFailure } from '../../store/selector'; import { createEmptyHostIsolationException } from '../../utils'; import { useHostIsolationExceptionsNavigateCallback, @@ -41,20 +53,26 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { const dispatch = useDispatch>(); const toasts = useToasts(); + const location = useHostIsolationExceptionsSelector(getCurrentLocation); + const creationInProgress = useHostIsolationExceptionsSelector((state) => isLoadingResourceState(state.form.status) ); const creationSuccessful = useHostIsolationExceptionsSelector((state) => isLoadedResourceState(state.form.status) ); - const creationFailure = useHostIsolationExceptionsSelector((state) => - isFailedResourceState(state.form.status) - ); + const creationFailure = useHostIsolationExceptionsSelector(getFormStatusFailure); + + const exceptionToEdit = useHostIsolationExceptionsSelector(getExceptionToEdit); const navigateCallback = useHostIsolationExceptionsNavigateCallback(); + const history = useHistory(); + const [formHasError, setFormHasError] = useState(true); - const [exception, setException] = useState(undefined); + const [exception, setException] = useState< + CreateExceptionListItemSchema | UpdateExceptionListItemSchema | undefined + >(undefined); const onCancel = useCallback( () => @@ -65,12 +83,31 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { [navigateCallback] ); + // load data to edit or create useEffect(() => { - setException(createEmptyHostIsolationException()); - }, []); + if (location.show === 'create' && exception === undefined) { + setException(createEmptyHostIsolationException()); + } else if (location.show === 'edit') { + // prevent flyout to show edit without an id + if (!location.id) { + onCancel(); + return; + } + // load the exception to edit + if (!exceptionToEdit || location.id !== exceptionToEdit.id) { + dispatch({ + type: 'hostIsolationExceptionsMarkToEdit', + payload: { id: location.id! }, + }); + } else { + setException(exceptionToEdit); + } + } + }, [dispatch, exception, exceptionToEdit, location.id, location.show, onCancel]); + // handle creation and edit success useEffect(() => { - if (creationSuccessful) { + if (creationSuccessful && exception?.name) { onCancel(); dispatch({ type: 'hostIsolationExceptionsFormStateChanged', @@ -78,30 +115,45 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { type: 'UninitialisedResourceState', }, }); - toasts.addSuccess( - i18n.translate( - 'xpack.securitySolution.hostIsolationExceptions.form.creationSuccessToastTitle', - { - defaultMessage: '"{name}" has been added to the host isolation exceptions list.', - values: { name: exception?.name }, - } - ) - ); + if (exception?.item_id) { + toasts.addSuccess(getUpdateSuccessMessage(exception.name)); + } else { + toasts.addSuccess(getCreationSuccessMessage(exception.name)); + } + } + }, [creationSuccessful, dispatch, exception?.item_id, exception?.name, onCancel, toasts]); + + // handle load item to edit error + useEffect(() => { + if (creationFailure && location.show === 'edit' && !exception?.item_id) { + toasts.addWarning(getLoadErrorMessage(creationFailure)); + history.replace(getHostIsolationExceptionsListPath(omit(location, ['show', 'id']))); + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); } - }, [creationSuccessful, onCancel, dispatch, toasts, exception?.name]); + }, [creationFailure, dispatch, exception?.item_id, history, location, toasts]); + // handle edit or creation error useEffect(() => { if (creationFailure) { - toasts.addDanger( - i18n.translate( - 'xpack.securitySolution.hostIsolationExceptions.form.creationFailureToastTitle', - { - defaultMessage: 'There was an error creating the exception', - } - ) - ); + // failed to load the entry + if (exception?.item_id) { + toasts.addDanger(getUpdateErrorMessage(creationFailure)); + } else { + toasts.addDanger(getCreateErrorMessage(creationFailure)); + } + dispatch({ + type: 'hostIsolationExceptionsFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); } - }, [dispatch, toasts, creationFailure]); + }, [creationFailure, dispatch, exception?.item_id, toasts]); const handleOnCancel = useCallback(() => { if (creationInProgress) return; @@ -109,10 +161,17 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { }, [creationInProgress, onCancel]); const handleOnSubmit = useCallback(() => { - dispatch({ - type: 'hostIsolationExceptionsCreateEntry', - payload: exception, - }); + if (exception?.item_id) { + dispatch({ + type: 'hostIsolationExceptionsSubmitEdit', + payload: exception, + }); + } else { + dispatch({ + type: 'hostIsolationExceptionsCreateEntry', + payload: exception, + }); + } }, [dispatch, exception]); const confirmButtonMemo = useMemo( @@ -124,13 +183,20 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { onClick={handleOnSubmit} isLoading={creationInProgress} > - + {exception?.item_id ? ( + + ) : ( + + )} ), - [formHasError, creationInProgress, handleOnSubmit] + [formHasError, creationInProgress, handleOnSubmit, exception?.item_id] ); return exception ? ( @@ -141,12 +207,21 @@ export const HostIsolationExceptionsFormFlyout: React.FC<{}> = memo(() => { > -

- -

+ {exception?.item_id ? ( +

+ +

+ ) : ( +

+ +

+ )}
diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts index df179c7a2221c..207e094453d90 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/translations.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { ServerApiError } from '../../../../../common/types'; export const NAME_PLACEHOLDER = i18n.translate( 'xpack.securitySolution.hostIsolationExceptions.form.name.placeholder', @@ -62,3 +63,67 @@ export const IP_ERROR = i18n.translate( defaultMessage: 'The ip is invalid. Only IPv4 with optional CIDR is supported', } ); + +export const DELETE_HOST_ISOLATION_EXCEPTION_LABEL = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.list.action.delete', + { + defaultMessage: 'Delete Exception', + } +); + +export const EDIT_HOST_ISOLATION_EXCEPTION_LABEL = i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.list.action.edit', + { + defaultMessage: 'Edit Exception', + } +); + +export const getCreateErrorMessage = (creationError: ServerApiError) => { + return i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.failedToastTitle.create', + { + defaultMessage: 'There was an error creating the exception: "{error}"', + values: { error: creationError.message }, + } + ); +}; + +export const getUpdateErrorMessage = (updateError: ServerApiError) => { + return i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.failedToastTitle.update', + { + defaultMessage: 'There was an error updating the exception: "{error}"', + values: { error: updateError.message }, + } + ); +}; + +export const getLoadErrorMessage = (getError: ServerApiError) => { + return i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.failedToastTitle.get', + { + defaultMessage: 'Unable to edit exception: "{error}"', + values: { error: getError.message }, + } + ); +}; + +export const getUpdateSuccessMessage = (name: string) => { + return i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.editingSuccessToastTitle', + { + defaultMessage: '"{name}" has been updated.', + values: { name }, + } + ); +}; + +export const getCreationSuccessMessage = (name: string) => { + return i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.form.creationSuccessToastTitle', + { + defaultMessage: '"{name}" has been added to the host isolation exceptions list.', + values: { name }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index cfb0121396e24..3c634a917c0ce 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -33,19 +33,16 @@ import { HostIsolationExceptionsEmptyState } from './components/empty'; import { HostIsolationExceptionsPageAction } from '../store/action'; import { HostIsolationExceptionDeleteModal } from './components/delete_modal'; import { HostIsolationExceptionsFormFlyout } from './components/form_flyout'; +import { + DELETE_HOST_ISOLATION_EXCEPTION_LABEL, + EDIT_HOST_ISOLATION_EXCEPTION_LABEL, +} from './components/translations'; type HostIsolationExceptionPaginatedContent = PaginatedContentProps< Immutable, typeof ExceptionItem >; -const DELETE_HOST_ISOLATION_EXCEPTION_LABEL = i18n.translate( - 'xpack.securitySolution.hostIsolationExceptions.list.actions.delete', - { - defaultMessage: 'Delete Exception', - } -); - export const HostIsolationExceptionsList = () => { const listItems = useHostIsolationExceptionsSelector(getListItems); const pagination = useHostIsolationExceptionsSelector(getListPagination); @@ -70,6 +67,17 @@ export const HostIsolationExceptionsList = () => { item: element, 'data-test-subj': `hostIsolationExceptionsCard`, actions: [ + { + icon: 'trash', + onClick: () => { + navigateCallback({ + show: 'edit', + id: element.id, + }); + }, + 'data-test-subj': 'editHostIsolationException', + children: EDIT_HOST_ISOLATION_EXCEPTION_LABEL, + }, { icon: 'trash', onClick: () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index f772986bff146..0ff6282f8a018 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -412,8 +412,8 @@ const fetchEditTrustedAppIfNeeded = async ( dispatch({ type: 'trustedAppCreationEditItemStateChanged', payload: { - type: 'LoadingResourceState', // @ts-expect-error-next-line will be fixed with when AsyncResourceState is refactored (#830) + type: 'LoadingResourceState', previousState: editItemState(currentState)!, }, }); From 5ee779c3c244d0be457a304d4708163f6cae26de Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 14 Oct 2021 11:42:14 +0100 Subject: [PATCH 12/31] remove stray semicolon (#114969) --- .../fleet/sections/agent_policy/details_page/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx index 76994feb18fea..c1d0e089fcc65 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx @@ -343,7 +343,7 @@ export const AgentPolicyDetailsPage: React.FunctionComponent = () => { /> )} - ; + ); }, [ From 06e469394a06d56c40a1a7e896da1d152c87bc5e Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Thu, 14 Oct 2021 13:15:02 +0200 Subject: [PATCH 13/31] cleanup (#114902) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../cypress/screens/alerts.ts | 11 ----- .../cypress/screens/alerts_details.ts | 6 --- .../cypress/screens/alerts_detection_rules.ts | 10 ----- .../cypress/screens/all_cases.ts | 6 --- .../cypress/screens/case_details.ts | 9 ----- .../cypress/screens/common/callouts.ts | 2 - .../cypress/screens/configure_cases.ts | 2 - .../cypress/screens/create_new_rule.ts | 11 ----- .../cypress/screens/edit_rule.ts | 1 - .../cypress/screens/exceptions.ts | 4 -- .../cypress/screens/fields_browser.ts | 11 ----- .../cypress/screens/hosts/events.ts | 10 ----- .../cypress/screens/hosts/main.ts | 2 - .../cypress/screens/rule_details.ts | 4 -- .../cypress/screens/shared.ts | 2 - .../cypress/screens/sourcerer.ts | 1 - .../cypress/screens/timeline.ts | 40 ------------------- .../cypress/screens/timelines.ts | 2 - .../security_solution/cypress/tasks/alerts.ts | 16 -------- .../cypress/tasks/alerts_detection_rules.ts | 25 +----------- .../cypress/tasks/case_details.ts | 13 +----- .../cypress/tasks/create_new_rule.ts | 5 --- .../cypress/tasks/exceptions.ts | 21 ---------- .../cypress/tasks/exceptions_table.ts | 5 --- .../cypress/tasks/fields_browser.ts | 13 ------ .../cypress/tasks/hosts/events.ts | 12 ------ .../cypress/tasks/security_header.ts | 4 -- .../cypress/tasks/security_main.ts | 8 ---- .../cypress/tasks/timeline.ts | 25 ------------ 29 files changed, 2 insertions(+), 279 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/cypress/screens/alerts.ts index d18a8e1ba10ab..0a815705f5b21 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts.ts @@ -16,8 +16,6 @@ export const ALERT_CHECKBOX = '[data-test-subj="select-event"].euiCheckbox__inpu export const ALERT_GRID_CELL = '[data-test-subj="dataGridRowCell"]'; -export const ALERT_ID = '[data-test-subj="draggable-content-_id"]'; - export const ALERT_RISK_SCORE_HEADER = '[data-test-subj="dataGridHeaderCell-signal.rule.risk_score"]'; @@ -45,26 +43,17 @@ export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-d export const MARK_ALERT_ACKNOWLEDGED_BTN = '[data-test-subj="acknowledged-alert-status"]'; -export const MARK_SELECTED_ALERTS_ACKNOWLEDGED_BTN = - '[data-test-subj="markSelectedAlertsAcknowledgedButton"]'; - export const NUMBER_OF_ALERTS = '[data-test-subj="events-viewer-panel"] [data-test-subj="server-side-event-count"]'; export const OPEN_ALERT_BTN = '[data-test-subj="open-alert-status"]'; -export const OPEN_SELECTED_ALERTS_BTN = '[data-test-subj="openSelectedAlertsButton"]'; - export const OPENED_ALERTS_FILTER_BTN = '[data-test-subj="openAlerts"]'; -export const SELECT_EVENT_CHECKBOX = '[data-test-subj="select-event"]'; - export const SELECTED_ALERTS = '[data-test-subj="selectedShowBulkActionsButton"]'; export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]'; -export const SHOWING_ALERTS = '[data-test-subj="showingAlerts"]'; - export const TAKE_ACTION_POPOVER_BTN = '[data-test-subj="selectedShowBulkActionsButton"]'; export const TIMELINE_CONTEXT_MENU_BTN = '[data-test-subj="timeline-context-menu-button"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts index 2c288aa59374c..c740a669d059a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -24,8 +24,6 @@ export const INVESTIGATION_TIME_ENRICHMENT_SECTION = export const JSON_VIEW_WRAPPER = '[data-test-subj="jsonViewWrapper"]'; -export const JSON_CONTENT = '[data-test-subj="jsonView"]'; - export const JSON_LINES = '.euiCodeBlock__line'; export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]'; @@ -36,16 +34,12 @@ export const TABLE_TAB = '[data-test-subj="tableTab"]'; export const TABLE_ROWS = '.euiTableRow'; -export const THREAT_CONTENT = '[data-test-subj^=draggable-content-]'; - export const THREAT_DETAILS_ACCORDION = '.euiAccordion__triggerWrapper'; export const THREAT_DETAILS_VIEW = '[data-test-subj="threat-details-view-0"]'; export const THREAT_INTEL_TAB = '[data-test-subj="threatIntelTab"]'; -export const THREAT_SUMMARY_VIEW = '[data-test-subj="threat-summary-view"]'; - export const TITLE = '.euiTitle'; export const UPDATE_ENRICHMENT_RANGE_BUTTON = '[data-test-subj="enrichment-button"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 315796a715cd3..39e08e29bdc2a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -21,14 +21,10 @@ export const DUPLICATE_RULE_ACTION_BTN = '[data-test-subj="duplicateRuleAction"] export const DUPLICATE_RULE_MENU_PANEL_BTN = '[data-test-subj="rules-details-duplicate-rule"]'; -export const REFRESH_BTN = '[data-test-subj="refreshRulesAction"] button'; - export const ACTIVATE_RULE_BULK_BTN = '[data-test-subj="activateRuleBulk"]'; export const DEACTIVATE_RULE_BULK_BTN = '[data-test-subj="deactivateRuleBulk"]'; -export const EXPORT_RULE_BULK_BTN = '[data-test-subj="exportRuleBulk"]'; - export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]'; export const DUPLICATE_RULE_BULK_BTN = '[data-test-subj="duplicateRuleBulk"]'; @@ -37,8 +33,6 @@ export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton" export const EXPORT_ACTION_BTN = '[data-test-subj="exportRuleAction"]'; -export const FIFTH_RULE = 4; - export const FIRST_RULE = 0; export const FOURTH_RULE = 3; @@ -72,8 +66,6 @@ export const RULES_ROW = '.euiTableRow'; export const RULES_MONIROTING_TABLE = '[data-test-subj="allRulesTableTab-monitoring"]'; -export const SEVENTH_RULE = 6; - export const SEVERITY = '[data-test-subj="severity"]'; export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]'; @@ -92,8 +84,6 @@ export const rowsPerPageSelector = (count: number) => export const pageSelector = (pageNumber: number) => `[data-test-subj="pagination-button-${pageNumber - 1}"]`; -export const NEXT_BTN = '[data-test-subj="pagination-button-next"]'; - export const SELECT_ALL_RULES_BTN = '[data-test-subj="selectAllRules"]'; export const RULES_EMPTY_PROMPT = '[data-test-subj="rulesEmptyPrompt"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts index fa6b6add57bac..9d653fb384a1a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts +++ b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts @@ -5,10 +5,6 @@ * 2.0. */ -export const ALL_CASES_CASE = (id: string) => { - return `[data-test-subj="cases-table-row-${id}"]`; -}; - export const ALL_CASES_CLOSED_CASES_STATS = '[data-test-subj="closedStatsHeader"]'; export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-commentCount"]'; @@ -19,8 +15,6 @@ export const ALL_CASES_CREATE_NEW_CASE_TABLE_BTN = '[data-test-subj="cases-table export const ALL_CASES_IN_PROGRESS_CASES_STATS = '[data-test-subj="inProgressStatsHeader"]'; -export const ALL_CASES_ITEM_ACTIONS_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]'; - export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]'; export const ALL_CASES_OPEN_CASES_COUNT = '[data-test-subj="case-status-filter"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/case_details.ts b/x-pack/plugins/security_solution/cypress/screens/case_details.ts index ef8f45b222dd0..8ec9a3fdacffc 100644 --- a/x-pack/plugins/security_solution/cypress/screens/case_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/case_details.ts @@ -5,16 +5,11 @@ * 2.0. */ -export const CASE_ACTIONS_BTN = '[data-test-subj="property-actions-ellipses"]'; - export const CASE_DETAILS_DESCRIPTION = '[data-test-subj="description-action"] [data-test-subj="user-action-markdown"]'; export const CASE_DETAILS_PAGE_TITLE = '[data-test-subj="header-page-title"]'; -export const CASE_DETAILS_PUSH_TO_EXTERNAL_SERVICE_BTN = - '[data-test-subj="push-to-external-service"]'; - export const CASE_DETAILS_STATUS = '[data-test-subj="case-view-status-dropdown"]'; export const CASE_DETAILS_TAGS = '[data-test-subj="case-tags"]'; @@ -34,10 +29,6 @@ export const CONNECTOR_CARD_DETAILS = '[data-test-subj="connector-card"]'; export const CONNECTOR_TITLE = '[data-test-subj="connector-card"] span.euiTitle'; -export const DELETE_CASE_BTN = '[data-test-subj="property-actions-trash"]'; - -export const DELETE_CASE_CONFIRMATION_BTN = '[data-test-subj="confirmModalConfirmButton"]'; - export const PARTICIPANTS = 1; export const REPORTER = 0; diff --git a/x-pack/plugins/security_solution/cypress/screens/common/callouts.ts b/x-pack/plugins/security_solution/cypress/screens/common/callouts.ts index 032d244f76977..d2dbf6da4aed2 100644 --- a/x-pack/plugins/security_solution/cypress/screens/common/callouts.ts +++ b/x-pack/plugins/security_solution/cypress/screens/common/callouts.ts @@ -5,8 +5,6 @@ * 2.0. */ -export const CALLOUT = '[data-test-subj^="callout-"]'; - export const callOutWithId = (id: string) => `[data-test-subj^="callout-${id}"]`; export const CALLOUT_DISMISS_BTN = '[data-test-subj^="callout-dismiss-"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/configure_cases.ts b/x-pack/plugins/security_solution/cypress/screens/configure_cases.ts index 1ad91ed0977a0..1014835f81efe 100644 --- a/x-pack/plugins/security_solution/cypress/screens/configure_cases.ts +++ b/x-pack/plugins/security_solution/cypress/screens/configure_cases.ts @@ -20,8 +20,6 @@ export const PASSWORD = '[data-test-subj="connector-servicenow-password-form-inp export const SAVE_BTN = '[data-test-subj="saveNewActionButton"]'; -export const SAVE_CHANGES_BTN = '[data-test-subj="case-configure-action-bottom-bar-save-button"]'; - export const SERVICE_NOW_CONNECTOR_CARD = '[data-test-subj=".servicenow-card"]'; export const TOASTER = '[data-test-subj="euiToastHeader"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 4748a48dbeb11..3510df6186870 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -58,8 +58,6 @@ export const COMBO_BOX_CLEAR_BTN = '[data-test-subj="comboBoxClearButton"]'; export const COMBO_BOX_INPUT = '[data-test-subj="comboBoxInput"]'; -export const COMBO_BOX_RESULT = '.euiFilterSelectItem'; - export const CREATE_AND_ACTIVATE_BTN = '[data-test-subj="create-activate"]'; export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; @@ -70,9 +68,6 @@ export const THREAT_MAPPING_COMBO_BOX_INPUT = export const THREAT_MATCH_CUSTOM_QUERY_INPUT = '[data-test-subj="detectionEngineStepDefineRuleQueryBar"] [data-test-subj="queryInput"]'; -export const THREAT_MATCH_INDICATOR_QUERY_INPUT = - '[data-test-subj="detectionEngineStepDefineRuleThreatMatchIndices"] [data-test-subj="queryInput"]'; - export const THREAT_MATCH_QUERY_INPUT = '[data-test-subj="detectionEngineStepDefineThreatRuleQueryBar"] [data-test-subj="queryInput"]'; @@ -206,12 +201,6 @@ export const SCHEDULE_INTERVAL_AMOUNT_INPUT = export const SCHEDULE_INTERVAL_UNITS_INPUT = '[data-test-subj="detectionEngineStepScheduleRuleInterval"] [data-test-subj="timeType"]'; -export const SCHEDULE_LOOKBACK_AMOUNT_INPUT = - '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="timeType"]'; - -export const SCHEDULE_LOOKBACK_UNITS_INPUT = - '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="schedule-units-input"]'; - export const SEVERITY_DROPDOWN = '[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts index 8d8520e109b15..73f3640071251 100644 --- a/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts @@ -7,5 +7,4 @@ export const EDIT_SUBMIT_BUTTON = '[data-test-subj="ruleEditSubmitButton"]'; export const BACK_TO_RULE_DETAILS = '[data-test-subj="ruleEditBackToRuleDetails"]'; -export const KIBANA_LOADING_INDICATOR = '[data-test-subj="globalLoadingIndicator"]'; export const KIBANA_LOADING_COMPLETE_INDICATOR = '[data-test-subj="globalLoadingIndicator-hidden"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts index bd6d3b7887206..e5027ee8b4f3a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts +++ b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts @@ -32,14 +32,10 @@ export const ADD_NESTED_BTN = '[data-test-subj="exceptionsNestedButton"]'; export const ENTRY_DELETE_BTN = '[data-test-subj="builderItemEntryDeleteButton"]'; -export const FIELD_INPUT_LIST_BTN = '[data-test-subj="comboBoxToggleListButton"]'; - export const CANCEL_BTN = '[data-test-subj="cancelExceptionAddButton"]'; export const BUILDER_MODAL_BODY = '[data-test-subj="exceptionsBuilderWrapper"]'; -export const EXCEPTIONS_TABLE_TAB = '[data-test-subj="allRulesTableTab-exceptions"]'; - export const EXCEPTIONS_TABLE = '[data-test-subj="exceptions-table"]'; export const EXCEPTIONS_TABLE_SEARCH = '[data-test-subj="exceptionsHeaderSearchInput"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts b/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts index 1115dfb00914e..4a5f813c301db 100644 --- a/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts +++ b/x-pack/plugins/security_solution/cypress/screens/fields_browser.ts @@ -15,14 +15,10 @@ export const FIELDS_BROWSER_CHECKBOX = (id: string) => { export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]'; -export const FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-name-host.geo.country_name"]`; - export const FIELDS_BROWSER_FIELDS_COUNT = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="fields-count"]`; export const FIELDS_BROWSER_FILTER_INPUT = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-search"]`; -export const FIELDS_BROWSER_HEADER_DROP_AREA = '[data-test-subj="headers-group"]'; - export const FIELDS_BROWSER_HOST_CATEGORIES_COUNT = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="host-category-count"]`; export const FIELDS_BROWSER_HOST_GEO_CITY_NAME_CHECKBOX = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-host.geo.city_name-checkbox"]`; @@ -35,11 +31,6 @@ export const FIELDS_BROWSER_HOST_GEO_CONTINENT_NAME_CHECKBOX = `${FIELDS_BROWSER export const FIELDS_BROWSER_HEADER_HOST_GEO_CONTINENT_NAME_HEADER = '[data-test-subj="timeline"] [data-test-subj="header-text-host.geo.continent_name"]'; -export const FIELDS_BROWSER_HOST_GEO_COUNTRY_NAME_CHECKBOX = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-host.geo.country_name-checkbox"]`; - -export const FIELDS_BROWSER_HOST_GEO_COUNTRY_NAME_HEADER = - '[data-test-subj="timeline"] [data-test-subj="header-text-host.geo.country_name"]'; - export const FIELDS_BROWSER_MESSAGE_CHECKBOX = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-message-checkbox"]`; export const FIELDS_BROWSER_MESSAGE_HEADER = @@ -47,8 +38,6 @@ export const FIELDS_BROWSER_MESSAGE_HEADER = export const FIELDS_BROWSER_RESET_FIELDS = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="reset-fields"]`; -export const FIELDS_BROWSER_TITLE = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="field-browser-title"]`; - export const FIELDS_BROWSER_SELECTED_CATEGORY_COUNT = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="selected-category-count-badge"]`; export const FIELDS_BROWSER_SELECTED_CATEGORY_TITLE = `${FIELDS_BROWSER_CONTAINER} [data-test-subj="selected-category-title"]`; diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts index de4acdd721c68..57de63b92a08b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts @@ -5,18 +5,11 @@ * 2.0. */ -export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]'; - export const EVENTS_VIEWER_FIELDS_BUTTON = '[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser"]'; -export const EVENTS_VIEWER_PANEL = '[data-test-subj="events-viewer-panel"]'; - export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]'; -export const HEADER_SUBTITLE = - '[data-test-subj="events-viewer-panel"] [data-test-subj="header-panel-subtitle"]'; - export const HOST_GEO_CITY_NAME_CHECKBOX = '[data-test-subj="field-host.geo.city_name-checkbox"]'; export const HOST_GEO_CITY_NAME_HEADER = @@ -32,9 +25,6 @@ export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]'; export const INSPECT_QUERY = '[data-test-subj="events-viewer-panel"] [data-test-subj="inspect-icon-button"]'; -export const LOAD_MORE = - '[data-test-subj="events-viewer-panel"] [data-test-subj="TimelineMoreButton"'; - export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; export const EVENTS_VIEWER_PAGINATION = diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts index 95381b06f44e9..4f1dd8387c63f 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/main.ts @@ -13,8 +13,6 @@ export const AUTHENTICATIONS_TAB = '[data-test-subj="navigation-authentications" export const EVENTS_TAB = '[data-test-subj="navigation-events"]'; -export const KQL_SEARCH_BAR = '[data-test-subj="queryInput"]'; - export const UNCOMMON_PROCESSES_TAB = '[data-test-subj="navigation-uncommonProcesses"]'; export const HOST_OVERVIEW = `[data-test-subj="host-overview"]`; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index 9bc22f35741d9..fb1fded1fe8a6 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -58,8 +58,6 @@ export const FIELDS_BROWSER_BTN = export const REFRESH_BUTTON = '[data-test-subj="refreshButton"]'; -export const RULE_ABOUT_DETAILS_HEADER_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]'; - export const RULE_NAME_HEADER = '[data-test-subj="header-page-title"]'; export const RULE_NAME_OVERRIDE_DETAILS = 'Rule name override'; @@ -83,8 +81,6 @@ export const RUNS_EVERY_DETAILS = 'Runs every'; export const SCHEDULE_DETAILS = '[data-test-subj=schedule] [data-test-subj="listItemColumnStepRuleDescription"]'; -export const SCHEDULE_STEP = '[data-test-subj="schedule"] .euiDescriptionList__description'; - export const SEVERITY_DETAILS = 'Severity'; export const TAGS_DETAILS = 'Tags'; diff --git a/x-pack/plugins/security_solution/cypress/screens/shared.ts b/x-pack/plugins/security_solution/cypress/screens/shared.ts index 99a0e423c563a..8a7ba48b1415d 100644 --- a/x-pack/plugins/security_solution/cypress/screens/shared.ts +++ b/x-pack/plugins/security_solution/cypress/screens/shared.ts @@ -5,6 +5,4 @@ * 2.0. */ -export const NOTIFICATION_TOASTS = '[data-test-subj="globalToastList"]'; - export const TOAST_ERROR = '.euiToast--danger'; diff --git a/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts b/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts index 142307735d340..874fc7352e908 100644 --- a/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts +++ b/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts @@ -28,4 +28,3 @@ export const SOURCERER_TIMELINE = { radioCustomLabel: '[data-test-subj="timeline-sourcerer-radio"] label.euiRadio__label[for="custom"]', }; -export const SOURCERER_TIMELINE_ADVANCED = '[data-test-subj="advanced-settings"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 4cf5d2f87f7a9..2e412bbed6fdc 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -26,8 +26,6 @@ export const CASE = (id: string) => { export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; -export const COLUMN_HEADERS = '[data-test-subj="column-headers"] [data-test-subj^=header-text]'; - export const COMBO_BOX = '.euiComboBoxOption__content'; export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; @@ -41,17 +39,8 @@ export const FAVORITE_TIMELINE = '[data-test-subj="timeline-favorite-filled-star export const FIELD_BROWSER = '[data-test-subj="show-field-browser"]'; -export const GRAPH_TAB_BUTTON = '[data-test-subj="timelineTabs-graph"]'; - -export const HEADER = '[data-test-subj="header"]'; - -export const HEADERS_GROUP = - '[data-test-subj="events-viewer-panel"] [data-test-subj="headers-group"]'; - export const ID_HEADER_FIELD = '[data-test-subj="timeline"] [data-test-subj="header-text-_id"]'; -export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name-_id"]'; - export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; export const ID_HOVER_ACTION_OVERFLOW_BTN = '[data-test-subj="more-actions-_id"]'; @@ -66,11 +55,6 @@ export const NOTE_CARD_CONTENT = '[data-test-subj="notes"]'; export const EVENT_NOTE = '[data-test-subj="timeline-notes-button-small"]'; -export const NOTE_BY_NOTE_ID = (noteId: string) => - `[data-test-subj="note-preview-${noteId}"] .euiMarkdownFormat`; - -export const NOTE_CONTENT = (noteId: string) => `${NOTE_BY_NOTE_ID(noteId)} p`; - export const NOTES_TEXT_AREA = '[data-test-subj="add-a-note"] textarea'; export const NOTES_TAB_BUTTON = '[data-test-subj="timelineTabs-notes"]'; @@ -96,12 +80,8 @@ export const OPEN_TIMELINE_TEMPLATE_ICON = export const PIN_EVENT = '[data-test-subj="pin"]'; -export const PINNED_TAB_BUTTON = '[data-test-subj="timelineTabs-pinned"]'; - export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; -export const REMOVE_COLUMN = '[data-test-subj="remove-column"]'; - export const RESET_FIELDS = '[data-test-subj="fields-browser-container"] [data-test-subj="reset-fields"]'; @@ -112,18 +92,6 @@ export const SEARCH_OR_FILTER_CONTAINER = export const INDICATOR_MATCH_ROW_RENDER = '[data-test-subj="threat-match-row"]'; -export const QUERY_TAB_EVENTS_TABLE = '[data-test-subj="query-events-table"]'; - -export const QUERY_TAB_EVENTS_BODY = '[data-test-subj="query-tab-flyout-body"]'; - -export const QUERY_TAB_EVENTS_FOOTER = '[data-test-subj="query-tab-flyout-footer"]'; - -export const PINNED_TAB_EVENTS_TABLE = '[data-test-subj="pinned-events-table"]'; - -export const PINNED_TAB_EVENTS_BODY = '[data-test-subj="pinned-tab-flyout-body"]'; - -export const PINNED_TAB_EVENTS_FOOTER = '[data-test-subj="pinned-tab-flyout-footer"]'; - export const QUERY_TAB_BUTTON = '[data-test-subj="timelineTabs-query"]'; export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; @@ -145,8 +113,6 @@ export const TIMELINE_CORRELATION_INPUT = '[data-test-subj="eqlQueryBarTextInput export const TIMELINE_CORRELATION_TAB = '[data-test-subj="timelineTabs-eql"]'; -export const TIMELINE_BOTTOM_BAR_CONTAINER = '[data-test-subj="timeline-bottom-bar-container"]'; - export const TIMELINE_DATA_PROVIDERS_ACTION_MENU = '[data-test-subj="providerActions"]'; export const TIMELINE_ADD_FIELD_BUTTON = '[data-test-subj="addField"]'; @@ -213,8 +179,6 @@ export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-plus-in-circle" export const TIMELINE_SEARCH_OR_FILTER = '[data-test-subj="timeline-select-search-or-filter"]'; -export const TIMELINE_SEARCH_OR_FILTER_CONTENT = '.searchOrFilterPopover'; - export const TIMELINE_KQLMODE_SEARCH = '[data-test-subj="kqlModePopoverSearch"]'; export const TIMELINE_KQLMODE_FILTER = '[data-test-subj="kqlModePopoverFilter"]'; @@ -257,10 +221,6 @@ export const TIMELINE_TABS = '[data-test-subj="timeline"] .euiTabs'; export const TIMELINE_TAB_CONTENT_EQL = '[data-test-subj="timeline-tab-content-eql"]'; -export const TIMELINE_TAB_CONTENT_QUERY = '[data-test-subj="timeline-tab-content-query"]'; - -export const TIMELINE_TAB_CONTENT_PINNED = '[data-test-subj="timeline-tab-content-pinned"]'; - export const TIMELINE_TAB_CONTENT_GRAPHS_NOTES = '[data-test-subj="timeline-tab-content-graph-notes"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timelines.ts b/x-pack/plugins/security_solution/cypress/screens/timelines.ts index ab6c790c599ab..ca60250330f83 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timelines.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timelines.ts @@ -27,6 +27,4 @@ export const TIMELINES_PINNED_EVENT_COUNT = '[data-test-subj="pinned-event-count export const TIMELINES_TABLE = '[data-test-subj="timelines-table"]'; -export const TIMELINES_USERNAME = '[data-test-subj="username"]'; - export const REFRESH_BUTTON = '[data-test-subj="refreshButton-linkIcon"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts index 5b8ad49f61b0d..067c9957189b9 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts @@ -17,13 +17,11 @@ import { LOADING_ALERTS_PANEL, MANAGE_ALERT_DETECTION_RULES_BTN, MARK_ALERT_ACKNOWLEDGED_BTN, - MARK_SELECTED_ALERTS_ACKNOWLEDGED_BTN, OPEN_ALERT_BTN, OPENED_ALERTS_FILTER_BTN, SEND_ALERT_TO_TIMELINE_BTN, TAKE_ACTION_POPOVER_BTN, TIMELINE_CONTEXT_MENU_BTN, - SELECT_EVENT_CHECKBOX, } from '../screens/alerts'; import { LOADING_INDICATOR, REFRESH_BUTTON } from '../screens/security_header'; import { TIMELINE_COLUMN_SPINNER } from '../screens/timeline'; @@ -73,10 +71,6 @@ export const expandFirstAlert = () => { export const viewThreatIntelTab = () => cy.get(THREAT_INTEL_TAB).click(); -export const viewThreatDetails = () => { - cy.get(EXPAND_ALERT_BTN).first().click({ force: true }); -}; - export const setEnrichmentDates = (from?: string, to?: string) => { cy.get(ENRICHMENT_QUERY_RANGE_PICKER).within(() => { if (from) { @@ -130,11 +124,6 @@ export const markAcknowledgedFirstAlert = () => { cy.get(MARK_ALERT_ACKNOWLEDGED_BTN).click(); }; -export const markAcknowledgedAlerts = () => { - cy.get(TAKE_ACTION_POPOVER_BTN).click({ force: true }); - cy.get(MARK_SELECTED_ALERTS_ACKNOWLEDGED_BTN).click(); -}; - export const selectNumberOfAlerts = (numberOfAlerts: number) => { for (let i = 0; i < numberOfAlerts; i++) { cy.get(ALERT_CHECKBOX).eq(i).click({ force: true }); @@ -174,8 +163,3 @@ export const waitForAlertsPanelToBeLoaded = () => { cy.get(LOADING_ALERTS_PANEL).should('exist'); cy.get(LOADING_ALERTS_PANEL).should('not.exist'); }; - -export const waitForAlertsToBeLoaded = () => { - const expectedNumberOfDisplayedAlerts = 25; - cy.get(SELECT_EVENT_CHECKBOX).should('have.length', expectedNumberOfDisplayedAlerts); -}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 6b985c7009b27..84b81108f8be3 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -27,7 +27,6 @@ import { SORT_RULES_BTN, EXPORT_ACTION_BTN, EDIT_RULE_ACTION_BTN, - NEXT_BTN, RULE_AUTO_REFRESH_IDLE_MODAL, RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE, rowsPerPageSelector, @@ -41,10 +40,9 @@ import { RULES_DELETE_CONFIRMATION_MODAL, ACTIVATE_RULE_BULK_BTN, DEACTIVATE_RULE_BULK_BTN, - EXPORT_RULE_BULK_BTN, RULE_DETAILS_DELETE_BTN, } from '../screens/alerts_detection_rules'; -import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details'; +import { ALL_ACTIONS } from '../screens/rule_details'; import { LOADING_INDICATOR } from '../screens/security_header'; export const activateRule = (rulePosition: number) => { @@ -97,11 +95,6 @@ export const deleteFirstRule = () => { cy.get(DELETE_RULE_ACTION_BTN).click(); }; -export const deleteRule = () => { - cy.get(ALL_ACTIONS).click(); - cy.get(DELETE_RULE).click(); -}; - export const deleteSelectedRules = () => { cy.get(BULK_ACTIONS_BTN).click({ force: true }); cy.get(DELETE_RULE_BULK_BTN).click(); @@ -137,11 +130,6 @@ export const deactivateSelectedRules = () => { cy.get(DEACTIVATE_RULE_BULK_BTN).click(); }; -export const exportSelectedRules = () => { - cy.get(BULK_ACTIONS_BTN).click({ force: true }); - cy.get(EXPORT_RULE_BULK_BTN).click(); -}; - export const exportFirstRule = () => { cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); cy.get(EXPORT_ACTION_BTN).click(); @@ -214,11 +202,6 @@ export const waitForRulesTableToBeRefreshed = () => { cy.get(RULES_TABLE_REFRESH_INDICATOR).should('not.exist'); }; -export const waitForRulesTableToBeAutoRefreshed = () => { - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('exist'); - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); -}; - export const waitForPrebuiltDetectionRulesToBeLoaded = () => { cy.get(LOAD_PREBUILT_RULES_BTN).should('not.exist'); cy.get(RULES_TABLE).should('exist'); @@ -273,9 +256,3 @@ export const goToPage = (pageNumber: number) => { cy.get(pageSelector(pageNumber)).last().click({ force: true }); waitForRulesTableToBeRefreshed(); }; - -export const goToNextPage = () => { - cy.get(RULES_TABLE_REFRESH_INDICATOR).should('not.exist'); - cy.get(NEXT_BTN).click({ force: true }); - waitForRulesTableToBeRefreshed(); -}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/case_details.ts b/x-pack/plugins/security_solution/cypress/tasks/case_details.ts index f64c3c9d79007..57f6f24ee890a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/case_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/case_details.ts @@ -6,18 +6,7 @@ */ import { TIMELINE_TITLE } from '../screens/timeline'; -import { - CASE_ACTIONS_BTN, - CASE_DETAILS_TIMELINE_LINK_MARKDOWN, - DELETE_CASE_BTN, - DELETE_CASE_CONFIRMATION_BTN, -} from '../screens/case_details'; - -export const deleteCase = () => { - cy.get(CASE_ACTIONS_BTN).first().click(); - cy.get(DELETE_CASE_BTN).click(); - cy.get(DELETE_CASE_CONFIRMATION_BTN).click(); -}; +import { CASE_DETAILS_TIMELINE_LINK_MARKDOWN } from '../screens/case_details'; export const openCaseTimeline = () => { cy.get(CASE_DETAILS_TIMELINE_LINK_MARKDOWN).click(); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index b7fb0785736f6..591be21b5682b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -32,7 +32,6 @@ import { CUSTOM_QUERY_REQUIRED, DEFAULT_RISK_SCORE_INPUT, DEFINE_CONTINUE_BUTTON, - DEFINE_EDIT_TAB, EQL_QUERY_INPUT, EQL_QUERY_PREVIEW_HISTOGRAM, EQL_QUERY_VALIDATION_SPINNER, @@ -495,10 +494,6 @@ export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRu cy.get(MACHINE_LEARNING_DROPDOWN_INPUT).should('not.exist'); }; -export const goToDefineStepTab = () => { - cy.get(DEFINE_EDIT_TAB).click({ force: true }); -}; - export const goToAboutStepTab = () => { cy.get(ABOUT_EDIT_TAB).click({ force: true }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/exceptions.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions.ts index 97e93ef8194a4..4548c921890c8 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/exceptions.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/exceptions.ts @@ -5,11 +5,9 @@ * 2.0. */ -import { Exception } from '../objects/exception'; import { FIELD_INPUT, OPERATOR_INPUT, - VALUES_INPUT, CANCEL_BTN, BUILDER_MODAL_BODY, EXCEPTION_ITEM_CONTAINER, @@ -38,25 +36,6 @@ export const addExceptionEntryOperatorValue = (operator: string, index = 0) => { cy.get(BUILDER_MODAL_BODY).click(); }; -export const addExceptionEntryValue = (values: string[], index = 0) => { - values.forEach((value) => { - cy.get(VALUES_INPUT).eq(index).type(`${value}{enter}`); - }); - cy.get(BUILDER_MODAL_BODY).click(); -}; - -export const addExceptionEntry = (exception: Exception, index = 0) => { - addExceptionEntryFieldValue(exception.field, index); - addExceptionEntryOperatorValue(exception.operator, index); - addExceptionEntryValue(exception.values, index); -}; - -export const addNestedExceptionEntry = (exception: Exception, index = 0) => { - addExceptionEntryFieldValue(exception.field, index); - addExceptionEntryOperatorValue(exception.operator, index); - addExceptionEntryValue(exception.values, index); -}; - export const closeExceptionBuilderModal = () => { cy.get(CANCEL_BTN).click(); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts index 838af066abd60..f2bc0c1f7e6ed 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts @@ -6,7 +6,6 @@ */ import { - EXCEPTIONS_TABLE_TAB, EXCEPTIONS_TABLE, EXCEPTIONS_TABLE_SEARCH, EXCEPTIONS_TABLE_DELETE_BTN, @@ -16,10 +15,6 @@ import { EXCEPTIONS_TABLE_EXPORT_BTN, } from '../screens/exceptions'; -export const goToExceptionsTable = () => { - cy.get(EXCEPTIONS_TABLE_TAB).should('exist').click({ force: true }); -}; - export const waitForExceptionsTableToBeLoaded = () => { cy.get(EXCEPTIONS_TABLE).should('exist'); cy.get(EXCEPTIONS_TABLE_SEARCH).should('exist'); diff --git a/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts b/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts index 72945f557ac1b..ee8bdb3b023dd 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/fields_browser.ts @@ -5,12 +5,8 @@ * 2.0. */ -import { drag, drop } from '../tasks/common'; - import { FIELDS_BROWSER_FILTER_INPUT, - FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER, - FIELDS_BROWSER_HEADER_DROP_AREA, FIELDS_BROWSER_HOST_GEO_CITY_NAME_CHECKBOX, FIELDS_BROWSER_HOST_GEO_CONTINENT_NAME_CHECKBOX, FIELDS_BROWSER_MESSAGE_CHECKBOX, @@ -37,15 +33,6 @@ export const addsHostGeoContinentNameToTimeline = () => { }); }; -export const addsHostGeoCountryNameToTimelineDraggingIt = () => { - cy.get(FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER).should('exist'); - cy.get(FIELDS_BROWSER_DRAGGABLE_HOST_GEO_COUNTRY_NAME_HEADER).then((field) => drag(field)); - - cy.get(FIELDS_BROWSER_HEADER_DROP_AREA) - .first() - .then((headersDropArea) => drop(headersDropArea)); -}; - export const clearFieldsBrowser = () => { cy.get(FIELDS_BROWSER_FILTER_INPUT).type('{selectall}{backspace}'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts index d40b43bac1e3f..bf8abe4328b96 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -7,14 +7,12 @@ import { drag, drop } from '../common'; import { - CLOSE_MODAL, EVENTS_VIEWER_FIELDS_BUTTON, EVENTS_VIEWER_PAGINATION, FIELDS_BROWSER_CONTAINER, HOST_GEO_CITY_NAME_CHECKBOX, HOST_GEO_COUNTRY_NAME_CHECKBOX, INSPECT_QUERY, - LOAD_MORE, SERVER_SIDE_EVENT_COUNT, } from '../../screens/hosts/events'; import { DATAGRID_HEADERS } from '../../screens/timeline'; @@ -32,19 +30,9 @@ export const addsHostGeoCountryNameToHeader = () => { }); }; -export const closeModal = () => { - cy.get(CLOSE_MODAL).click(); -}; - -export const loadMoreEvents = () => { - cy.get(LOAD_MORE).click({ force: true }); -}; - export const openEventsViewerFieldsBrowser = () => { cy.get(EVENTS_VIEWER_FIELDS_BUTTON).click({ force: true }); - cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', '0'); - cy.get(FIELDS_BROWSER_CONTAINER).should('exist'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/security_header.ts b/x-pack/plugins/security_solution/cypress/tasks/security_header.ts index 0d6ab9449da86..558b750e2641b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/security_header.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/security_header.ts @@ -22,7 +22,3 @@ export const navigateFromHeaderTo = (page: string) => { export const refreshPage = () => { cy.get(REFRESH_BUTTON).click({ force: true }).should('not.have.text', 'Updating'); }; - -export const waitForThePageToBeUpdated = () => { - cy.get(REFRESH_BUTTON).should('not.have.text', 'Updating'); -}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/security_main.ts b/x-pack/plugins/security_solution/cypress/tasks/security_main.ts index 01651b7b943d0..9b8af6c5ceef6 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/security_main.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/security_main.ts @@ -7,7 +7,6 @@ import { CLOSE_TIMELINE_BUTTON, - MAIN_PAGE, TIMELINE_TOGGLE_BUTTON, TIMELINE_BOTTOM_BAR_TOGGLE_BUTTON, } from '../screens/security_main'; @@ -25,13 +24,6 @@ export const closeTimelineUsingCloseButton = () => { cy.get(CLOSE_TIMELINE_BUTTON).filter(':visible').click(); }; -export const openTimelineIfClosed = () => - cy.get(MAIN_PAGE).then(($page) => { - if ($page.find(TIMELINE_BOTTOM_BAR_TOGGLE_BUTTON).length === 1) { - openTimelineUsingToggle(); - } - }); - export const enterFullScreenMode = () => { cy.get(TIMELINE_FULL_SCREEN_BUTTON).first().click({ force: true }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 039e8ed44886e..4c6b73de80940 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -22,7 +22,6 @@ import { COMBO_BOX, CREATE_NEW_TIMELINE, FIELD_BROWSER, - ID_FIELD, ID_HEADER_FIELD, ID_TOGGLE_FIELD, ID_HOVER_ACTION_OVERFLOW_BTN, @@ -69,8 +68,6 @@ import { } from '../screens/timeline'; import { REFRESH_BUTTON, TIMELINE } from '../screens/timelines'; -import { drag, drop } from '../tasks/common'; - import { closeFieldsBrowser, filterFieldsBrowser } from '../tasks/fields_browser'; export const hostExistsQuery = 'host.name: *'; @@ -121,10 +118,6 @@ export const goToCorrelationTab = () => { return cy.root().find(TIMELINE_CORRELATION_TAB); }; -export const getNotePreviewByNoteId = (noteId: string) => { - return cy.get(`[data-test-subj="note-preview-${noteId}"]`); -}; - export const goToQueryTab = () => { cy.root() .pipe(($el) => { @@ -302,10 +295,6 @@ export const populateTimeline = () => { cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', '0'); }; -export const unpinFirstEvent = () => { - cy.get(PIN_EVENT).first().click({ force: true }); -}; - const clickTimestampHoverActionOverflowButton = () => { cy.get(TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN).should('exist'); @@ -320,16 +309,6 @@ export const clickTimestampToggleField = () => { cy.get(TIMESTAMP_TOGGLE_FIELD).click({ force: true }); }; -export const dragAndDropIdToggleFieldToTimeline = () => { - cy.get(ID_HEADER_FIELD).should('not.exist'); - - cy.get(ID_FIELD).then((field) => drag(field)); - - cy.get(`[data-test-subj="timeline"] [data-test-subj="headers-group"]`) - .first() - .then((headersDropArea) => drop(headersDropArea)); -}; - export const removeColumn = (columnName: string) => { cy.get(FIELD_BROWSER).first().click(); filterFieldsBrowser(columnName); @@ -350,10 +329,6 @@ export const waitForTimelineChanges = () => { cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('not.exist'); }; -export const waitForEventsPanelToBeLoaded = () => { - cy.get(QUERY_TAB_BUTTON).find('.euiBadge').should('exist'); -}; - /** * We keep clicking on the refresh button until we have the timeline we are looking * for. NOTE: That because refresh happens so fast, the click handler in most cases From 187d9496857f2fae4395caf1d62d2a6ebb2eb0fb Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Thu, 14 Oct 2021 07:19:50 -0400 Subject: [PATCH 14/31] [Fleet] Improve Functionality around Managed Package Policies (#114526) * Enabled auto policy upgrades for APM and Synthetics * fixup! Enabled auto policy upgrades for APM and Synthetics * Rework preconfiguration policy upgrade flow + report errors * Fix type error in test * Fix type errors + tests * wip * Remove keep policies up to date checks * Remove references to KEEP_POLICIES_UP_TO_DATE_PACKAGES * Move package policy upgrade results to nonFatalErrors * Fix types * Fix type error Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/routes/setup/handlers.test.ts | 5 +- .../fleet/server/routes/setup/handlers.ts | 20 +++- .../services/managed_package_policies.test.ts | 104 +++++++++++++++++- .../services/managed_package_policies.ts | 65 ++++++++--- .../fleet/server/services/preconfiguration.ts | 22 ++-- x-pack/plugins/fleet/server/services/setup.ts | 5 +- 6 files changed, 186 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index bd82989a9e828..c196054faf08c 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -53,7 +53,10 @@ describe('FleetSetupHandler', () => { ); await fleetSetupHandler(context, request, response); - const expectedBody: PostFleetSetupResponse = { isInitialized: true, nonFatalErrors: [] }; + const expectedBody: PostFleetSetupResponse = { + isInitialized: true, + nonFatalErrors: [], + }; expect(response.customError).toHaveBeenCalledTimes(0); expect(response.ok).toHaveBeenCalledWith({ body: expectedBody }); }); diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index 6311b9d970d35..d24db96667d52 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -49,13 +49,21 @@ export const fleetSetupHandler: RequestHandler = async (context, request, respon const setupStatus = await setupFleet(soClient, esClient); const body: PostFleetSetupResponse = { ...setupStatus, - nonFatalErrors: setupStatus.nonFatalErrors.map((e) => { + nonFatalErrors: setupStatus.nonFatalErrors.flatMap((e) => { // JSONify the error object so it can be displayed properly in the UI - const error = e.error ?? e; - return { - name: error.name, - message: error.message, - }; + if ('error' in e) { + return { + name: e.error.name, + message: e.error.message, + }; + } else { + return e.errors.map((upgradePackagePolicyError: any) => { + return { + name: upgradePackagePolicyError.key, + message: upgradePackagePolicyError.message, + }; + }); + } }), }; diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts index a53b1fe648905..52c1c71446d64 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts @@ -18,7 +18,7 @@ jest.mock('./app_context', () => { ...jest.requireActual('./app_context'), appContextService: { getLogger: jest.fn(() => { - return { debug: jest.fn() }; + return { error: jest.fn() }; }), }, }; @@ -27,7 +27,9 @@ jest.mock('./app_context', () => { describe('managed package policies', () => { afterEach(() => { (packagePolicyService.get as jest.Mock).mockReset(); + (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockReset(); (getPackageInfo as jest.Mock).mockReset(); + (packagePolicyService.upgrade as jest.Mock).mockReset(); }); it('should not upgrade policies for non-managed package', async () => { @@ -54,6 +56,16 @@ describe('managed package policies', () => { } ); + (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockImplementationOnce( + (savedObjectsClient: any, id: string) => { + return { + name: 'non-managed-package-policy', + diff: [{ id: 'foo' }, { id: 'bar' }], + hasErrors: false, + }; + } + ); + (getPackageInfo as jest.Mock).mockImplementationOnce( ({ savedObjectsClient, pkgName, pkgVersion }) => ({ name: pkgName, @@ -91,6 +103,16 @@ describe('managed package policies', () => { } ); + (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockImplementationOnce( + (savedObjectsClient: any, id: string) => { + return { + name: 'non-managed-package-policy', + diff: [{ id: 'foo' }, { id: 'bar' }], + hasErrors: false, + }; + } + ); + (getPackageInfo as jest.Mock).mockImplementationOnce( ({ savedObjectsClient, pkgName, pkgVersion }) => ({ name: pkgName, @@ -103,4 +125,84 @@ describe('managed package policies', () => { expect(packagePolicyService.upgrade).toBeCalledWith(soClient, esClient, ['managed-package-id']); }); + + describe('when dry run reports conflicts', () => { + it('should return errors + diff without performing upgrade', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const soClient = savedObjectsClientMock.create(); + + (packagePolicyService.get as jest.Mock).mockImplementationOnce( + (savedObjectsClient: any, id: string) => { + return { + id, + inputs: {}, + version: '', + revision: 1, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + package: { + name: 'conflicting-package', + title: 'Conflicting Package', + version: '0.0.1', + }, + }; + } + ); + + (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockImplementationOnce( + (savedObjectsClient: any, id: string) => { + return { + name: 'conflicting-package-policy', + diff: [ + { id: 'foo' }, + { id: 'bar', errors: [{ key: 'some.test.value', message: 'Conflict detected' }] }, + ], + hasErrors: true, + }; + } + ); + + (getPackageInfo as jest.Mock).mockImplementationOnce( + ({ savedObjectsClient, pkgName, pkgVersion }) => ({ + name: pkgName, + version: pkgVersion, + keepPoliciesUpToDate: true, + }) + ); + + const result = await upgradeManagedPackagePolicies(soClient, esClient, [ + 'conflicting-package-policy', + ]); + + expect(result).toEqual([ + { + packagePolicyId: 'conflicting-package-policy', + diff: [ + { + id: 'foo', + }, + { + id: 'bar', + errors: [ + { + key: 'some.test.value', + message: 'Conflict detected', + }, + ], + }, + ], + errors: [ + { + key: 'some.test.value', + message: 'Conflict detected', + }, + ], + }, + ]); + + expect(packagePolicyService.upgrade).not.toBeCalled(); + }); + }); }); diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.ts index 73f85525f4c60..25e2482892712 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.ts @@ -7,12 +7,19 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; +import type { UpgradePackagePolicyDryRunResponseItem } from '../../common'; import { AUTO_UPDATE_PACKAGES } from '../../common'; import { appContextService } from './app_context'; -import { getPackageInfo } from './epm/packages'; +import { getInstallation, getPackageInfo } from './epm/packages'; import { packagePolicyService } from './package_policy'; +export interface UpgradeManagedPackagePoliciesResult { + packagePolicyId: string; + diff: UpgradePackagePolicyDryRunResponseItem['diff']; + errors: any; +} + /** * Upgrade any package policies for packages installed through setup that are denoted as `AUTO_UPGRADE` packages * or have the `keep_policies_up_to_date` flag set to `true` @@ -21,8 +28,8 @@ export const upgradeManagedPackagePolicies = async ( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, packagePolicyIds: string[] -) => { - const policyIdsToUpgrade: string[] = []; +): Promise => { + const results: UpgradeManagedPackagePoliciesResult[] = []; for (const packagePolicyId of packagePolicyIds) { const packagePolicy = await packagePolicyService.get(soClient, packagePolicyId); @@ -37,22 +44,50 @@ export const upgradeManagedPackagePolicies = async ( pkgVersion: packagePolicy.package.version, }); + const installedPackage = await getInstallation({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + }); + + const isPolicyVersionAlignedWithInstalledVersion = + packageInfo.version === installedPackage?.version; + const shouldUpgradePolicies = - AUTO_UPDATE_PACKAGES.some((pkg) => pkg.name === packageInfo.name) || - packageInfo.keepPoliciesUpToDate; + !isPolicyVersionAlignedWithInstalledVersion && + (AUTO_UPDATE_PACKAGES.some((pkg) => pkg.name === packageInfo.name) || + packageInfo.keepPoliciesUpToDate); if (shouldUpgradePolicies) { - policyIdsToUpgrade.push(packagePolicy.id); - } - } - - if (policyIdsToUpgrade.length) { - appContextService - .getLogger() - .debug( - `Upgrading ${policyIdsToUpgrade.length} package policies: ${policyIdsToUpgrade.join(', ')}` + // Since upgrades don't report diffs/errors, we need to perform a dry run first in order + // to notify the user of any granular policy upgrade errors that occur during Fleet's + // preconfiguration check + const dryRunResults = await packagePolicyService.getUpgradeDryRunDiff( + soClient, + packagePolicyId ); - await packagePolicyService.upgrade(soClient, esClient, policyIdsToUpgrade); + if (dryRunResults.hasErrors) { + const errors = dryRunResults.diff?.[1].errors; + appContextService + .getLogger() + .error( + new Error( + `Error upgrading package policy ${packagePolicyId}: ${JSON.stringify(errors)}` + ) + ); + + results.push({ packagePolicyId, diff: dryRunResults.diff, errors }); + continue; + } + + try { + await packagePolicyService.upgrade(soClient, esClient, [packagePolicyId]); + results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [] }); + } catch (error) { + results.push({ packagePolicyId, diff: dryRunResults.diff, errors: [error] }); + } + } } + + return results; }; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index a878af64aa05e..3b322e1112d6a 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -35,13 +35,14 @@ import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; import type { InputsOverride } from './package_policy'; import { overridePackageInputs } from './package_policy'; import { appContextService } from './app_context'; +import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; import { upgradeManagedPackagePolicies } from './managed_package_policies'; import { outputService } from './output'; interface PreconfigurationResult { policies: Array<{ id: string; updated_at: string }>; packages: string[]; - nonFatalErrors: PreconfigurationError[]; + nonFatalErrors: Array; } function isPreconfiguredOutputDifferentFromCurrent( @@ -326,16 +327,15 @@ export async function ensurePreconfiguredPackagesAndPolicies( } } - try { - const fulfilledPolicyPackagePolicyIds = fulfilledPolicies.flatMap( - ({ policy }) => policy?.package_policies as string[] - ); + const fulfilledPolicyPackagePolicyIds = fulfilledPolicies + .filter(({ policy }) => policy?.package_policies) + .flatMap(({ policy }) => policy?.package_policies as string[]); - await upgradeManagedPackagePolicies(soClient, esClient, fulfilledPolicyPackagePolicyIds); - // Swallow errors that occur when upgrading - } catch (error) { - appContextService.getLogger().error(error); - } + const packagePolicyUpgradeResults = await upgradeManagedPackagePolicies( + soClient, + esClient, + fulfilledPolicyPackagePolicyIds + ); return { policies: fulfilledPolicies.map((p) => @@ -353,7 +353,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( } ), packages: fulfilledPackages.map((pkg) => pkgToPkgKey(pkg)), - nonFatalErrors: [...rejectedPackages, ...rejectedPolicies], + nonFatalErrors: [...rejectedPackages, ...rejectedPolicies, ...packagePolicyUpgradeResults], }; } diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 08c580d80c804..37d79c1bb691d 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -32,10 +32,13 @@ import { ensureDefaultComponentTemplate } from './epm/elasticsearch/template/ins import { getInstallations, installPackage } from './epm/packages'; import { isPackageInstalled } from './epm/packages/install'; import { pkgToPkgKey } from './epm/registry'; +import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; export interface SetupStatus { isInitialized: boolean; - nonFatalErrors: Array; + nonFatalErrors: Array< + PreconfigurationError | DefaultPackagesInstallationError | UpgradeManagedPackagePoliciesResult + >; } export async function setupFleet( From f598cf1ffd83d61fdc6c5205d5a9e25721f799dd Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 14 Oct 2021 13:46:16 +0200 Subject: [PATCH 15/31] Update app services bundle limits (#114789) --- packages/kbn-optimizer/limits.yml | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 068408bce40fd..f331bb6dae444 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -2,7 +2,6 @@ pageLoadAssetSize: advancedSettings: 27596 alerting: 106936 apm: 64385 - bfetch: 51874 canvas: 1066647 charts: 95000 cloud: 21076 @@ -11,17 +10,12 @@ pageLoadAssetSize: crossClusterReplication: 65408 dashboard: 374194 dashboardEnhanced: 65646 - data: 824229 - dataEnhanced: 50420 devTools: 38637 discover: 99999 discoverEnhanced: 42730 - embeddable: 99999 - embeddableEnhanced: 41145 enterpriseSearch: 35741 esUiShared: 326654 features: 21723 - fieldFormats: 92628 globalSearch: 29696 globalSearchBar: 50403 globalSearchProviders: 25554 @@ -30,8 +24,6 @@ pageLoadAssetSize: home: 30182 indexLifecycleManagement: 107090 indexManagement: 140608 - indexPatternManagement: 28222 - indexPatternEditor: 25000 infra: 184320 fleet: 250000 ingestPipelines: 58003 @@ -39,8 +31,6 @@ pageLoadAssetSize: inspector: 148711 kibanaLegacy: 107711 kibanaOverview: 56279 - kibanaReact: 188705 - kibanaUtils: 198829 lens: 96624 licenseManagement: 41817 licensing: 29004 @@ -55,7 +45,6 @@ pageLoadAssetSize: observability: 89709 painlessLab: 179748 remoteClusters: 51327 - reporting: 183418 rollup: 97204 savedObjects: 108518 savedObjectsManagement: 101836 @@ -63,18 +52,14 @@ pageLoadAssetSize: savedObjectsTaggingOss: 20590 searchprofiler: 67080 security: 95864 - share: 99061 snapshotRestore: 79032 spaces: 57868 telemetry: 51957 telemetryManagementSection: 38586 transform: 41007 triggersActionsUi: 100000 - uiActions: 97717 - uiActionsEnhanced: 32000 upgradeAssistant: 81241 uptime: 40825 - urlDrilldown: 70674 urlForwarding: 32579 usageCollection: 39762 visDefaultEditor: 50178 @@ -92,7 +77,6 @@ pageLoadAssetSize: runtimeFields: 41752 stackAlerts: 29684 presentationUtil: 94301 - indexPatternFieldEditor: 50000 osquery: 107090 fileUpload: 25664 dataVisualizer: 27530 @@ -110,9 +94,25 @@ pageLoadAssetSize: expressionShape: 34008 interactiveSetup: 80000 expressionTagcloud: 27505 - expressions: 239290 securitySolution: 231753 customIntegrations: 28810 expressionMetricVis: 23121 visTypeMetric: 23332 - dataViews: 42000 + bfetch: 22837 + kibanaUtils: 97808 + data: 491273 + dataViews: 41532 + expressions: 140958 + fieldFormats: 65209 + kibanaReact: 99422 + share: 71239 + uiActions: 35121 + dataEnhanced: 24980 + embeddable: 87309 + embeddableEnhanced: 22107 + uiActionsEnhanced: 38494 + urlDrilldown: 30063 + indexPatternEditor: 19123 + indexPatternFieldEditor: 34448 + indexPatternManagement: 19165 + reporting: 57003 From 29d750a8c1a319b18a2c1942d480b5bc66add010 Mon Sep 17 00:00:00 2001 From: Kevin Lacabane Date: Thu, 14 Oct 2021 14:33:07 +0200 Subject: [PATCH 16/31] [Stack Monitoring] fix feature controls functional test (#114781) * fix feature controls functional test * target test-subj attr instead of class --- .../monitoring/public/application/pages/page_template.tsx | 2 +- x-pack/plugins/monitoring/public/directives/main/index.html | 2 +- .../apps/monitoring/feature_controls/monitoring_spaces.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index 5c030814d9cdf..23eeb2c034a80 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -92,7 +92,7 @@ export const PageTemplate: React.FC = ({ }; return ( -
+
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html index 558ed5e874cd6..c989c71d8c1d4 100644 --- a/x-pack/plugins/monitoring/public/directives/main/index.html +++ b/x-pack/plugins/monitoring/public/directives/main/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index f2b872bccbaa7..71f100b49068f 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -52,7 +52,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { basePath: '/s/custom_space', }); - const exists = await find.existsByCssSelector('monitoring-main'); + const exists = await find.existsByCssSelector('[data-test-subj="monitoringAppContainer"]'); expect(exists).to.be(true); }); }); From b21e1ebf3806793f09770cc03d7a65a83fac5e17 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Thu, 14 Oct 2021 14:43:06 +0200 Subject: [PATCH 17/31] Deprecate DataView.flattenHit in favor of data plugin flattenHit (#114517) * WIP replacing indexPattern.flattenHit by tabify * Fix jest tests * Read metaFields from index pattern * Remove old test code * remove unnecessary changes * Remove flattenHitWrapper APIs * Fix imports * Fix missing metaFields * Add all meta fields to allowlist * Improve inline comments * Move flattenHit test to new implementation * Add deprecation comment to implementation Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/tabify_docs.test.ts.snap | 160 +---------------- .../data/common/search/tabify/index.ts | 2 +- .../common/search/tabify/tabify_docs.test.ts | 170 ++++++++++++------ .../data/common/search/tabify/tabify_docs.ts | 85 ++++++++- src/plugins/data/public/index.ts | 2 - .../data_views/common/data_views/data_view.ts | 3 + .../common/data_views/flatten_hit.ts | 17 +- src/plugins/data_views/public/index.ts | 2 +- .../public/__mocks__/index_pattern.ts | 9 +- .../__mocks__/index_pattern_with_timefield.ts | 9 +- .../doc_table/components/table_row.tsx | 3 +- .../sidebar/discover_sidebar.test.tsx | 4 +- .../discover_sidebar_responsive.test.tsx | 6 +- .../sidebar/lib/field_calculator.js | 4 +- .../sidebar/lib/field_calculator.test.ts | 3 +- .../apps/main/utils/calc_field_counts.ts | 4 +- .../discover_grid/discover_grid.tsx | 4 +- .../discover_grid_cell_actions.tsx | 6 +- .../discover_grid_document_selection.test.tsx | 45 ++--- .../discover_grid_expand_button.test.tsx | 36 ++-- .../get_render_cell_value.test.tsx | 33 ++-- .../components/table/table.test.tsx | 6 +- .../application/components/table/table.tsx | 3 +- .../generate_csv/generate_csv.test.ts | 1 + .../generate_csv/generate_csv.ts | 2 +- 25 files changed, 293 insertions(+), 326 deletions(-) diff --git a/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap b/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap index 22276335a0599..9a85ba57ce5ef 100644 --- a/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap +++ b/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`tabifyDocs combines meta fields if meta option is set 1`] = ` +exports[`tabify_docs tabifyDocs converts fields by default 1`] = ` Object { "columns": Array [ Object { @@ -108,7 +108,7 @@ Object { } `; -exports[`tabifyDocs converts fields by default 1`] = ` +exports[`tabify_docs tabifyDocs converts source if option is set 1`] = ` Object { "columns": Array [ Object { @@ -216,7 +216,7 @@ Object { } `; -exports[`tabifyDocs converts source if option is set 1`] = ` +exports[`tabify_docs tabifyDocs skips nested fields if option is set 1`] = ` Object { "columns": Array [ Object { @@ -324,115 +324,7 @@ Object { } `; -exports[`tabifyDocs skips nested fields if option is set 1`] = ` -Object { - "columns": Array [ - Object { - "id": "fieldTest", - "meta": Object { - "field": "fieldTest", - "index": "test-index", - "params": Object { - "id": "number", - }, - "type": "number", - }, - "name": "fieldTest", - }, - Object { - "id": "invalidMapping", - "meta": Object { - "field": "invalidMapping", - "index": "test-index", - "params": undefined, - "type": "number", - }, - "name": "invalidMapping", - }, - Object { - "id": "nested", - "meta": Object { - "field": "nested", - "index": "test-index", - "params": undefined, - "type": "object", - }, - "name": "nested", - }, - Object { - "id": "sourceTest", - "meta": Object { - "field": "sourceTest", - "index": "test-index", - "params": Object { - "id": "number", - }, - "type": "number", - }, - "name": "sourceTest", - }, - Object { - "id": "_id", - "meta": Object { - "field": "_id", - "index": "test-index", - "params": undefined, - "type": "string", - }, - "name": "_id", - }, - Object { - "id": "_index", - "meta": Object { - "field": "_index", - "index": "test-index", - "params": undefined, - "type": "string", - }, - "name": "_index", - }, - Object { - "id": "_score", - "meta": Object { - "field": "_score", - "index": "test-index", - "params": undefined, - "type": "number", - }, - "name": "_score", - }, - Object { - "id": "_type", - "meta": Object { - "field": "_type", - "index": "test-index", - "params": undefined, - "type": "string", - }, - "name": "_type", - }, - ], - "rows": Array [ - Object { - "_id": "hit-id-value", - "_index": "hit-index-value", - "_score": 77, - "_type": "hit-type-value", - "fieldTest": 123, - "invalidMapping": 345, - "nested": Array [ - Object { - "field": 123, - }, - ], - "sourceTest": 123, - }, - ], - "type": "datatable", -} -`; - -exports[`tabifyDocs works without provided index pattern 1`] = ` +exports[`tabify_docs tabifyDocs works without provided index pattern 1`] = ` Object { "columns": Array [ Object { @@ -475,53 +367,9 @@ Object { }, "name": "sourceTest", }, - Object { - "id": "_id", - "meta": Object { - "field": "_id", - "index": undefined, - "params": undefined, - "type": "string", - }, - "name": "_id", - }, - Object { - "id": "_index", - "meta": Object { - "field": "_index", - "index": undefined, - "params": undefined, - "type": "string", - }, - "name": "_index", - }, - Object { - "id": "_score", - "meta": Object { - "field": "_score", - "index": undefined, - "params": undefined, - "type": "number", - }, - "name": "_score", - }, - Object { - "id": "_type", - "meta": Object { - "field": "_type", - "index": undefined, - "params": undefined, - "type": "string", - }, - "name": "_type", - }, ], "rows": Array [ Object { - "_id": "hit-id-value", - "_index": "hit-index-value", - "_score": 77, - "_type": "hit-type-value", "fieldTest": 123, "invalidMapping": 345, "nested": Array [ diff --git a/src/plugins/data/common/search/tabify/index.ts b/src/plugins/data/common/search/tabify/index.ts index 74fbc7ba4cfa4..279ff705f231c 100644 --- a/src/plugins/data/common/search/tabify/index.ts +++ b/src/plugins/data/common/search/tabify/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export { tabifyDocs } from './tabify_docs'; +export { tabifyDocs, flattenHit } from './tabify_docs'; export { tabifyAggResponse } from './tabify'; export { tabifyGetColumns } from './get_columns'; diff --git a/src/plugins/data/common/search/tabify/tabify_docs.test.ts b/src/plugins/data/common/search/tabify/tabify_docs.test.ts index 1a3f7195b722e..a2910a1be4a9a 100644 --- a/src/plugins/data/common/search/tabify/tabify_docs.test.ts +++ b/src/plugins/data/common/search/tabify/tabify_docs.test.ts @@ -6,71 +6,137 @@ * Side Public License, v 1. */ -import { tabifyDocs } from './tabify_docs'; -import { IndexPattern } from '../..'; +import { tabifyDocs, flattenHit } from './tabify_docs'; +import { IndexPattern, DataView } from '../..'; import type { estypes } from '@elastic/elasticsearch'; -describe('tabifyDocs', () => { - const fieldFormats = { - getInstance: (id: string) => ({ toJSON: () => ({ id }) }), - getDefaultInstance: (id: string) => ({ toJSON: () => ({ id }) }), - }; +import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; +import { stubbedSavedObjectIndexPattern } from '../../../../data_views/common/data_view.stub'; - const index = new IndexPattern({ +class MockFieldFormatter {} + +fieldFormatsMock.getInstance = jest.fn().mockImplementation(() => new MockFieldFormatter()) as any; + +// helper function to create index patterns +function create(id: string) { + const { + type, + version, + attributes: { timeFieldName, fields, title }, + } = stubbedSavedObjectIndexPattern(id); + + return new DataView({ spec: { - id: 'test-index', - fields: { - sourceTest: { name: 'sourceTest', type: 'number', searchable: true, aggregatable: true }, - fieldTest: { name: 'fieldTest', type: 'number', searchable: true, aggregatable: true }, - 'nested.field': { - name: 'nested.field', - type: 'number', - searchable: true, - aggregatable: true, - }, - }, + id, + type, + version, + timeFieldName, + fields: JSON.parse(fields), + title, + runtimeFieldMap: {}, }, - fieldFormats: fieldFormats as any, + fieldFormats: fieldFormatsMock, + shortDotsEnable: false, + metaFields: ['_id', '_type', '_score', '_routing'], }); +} - // @ts-expect-error not full inteface - const response = { - hits: { - hits: [ +describe('tabify_docs', () => { + describe('flattenHit', () => { + let indexPattern: DataView; + + // create an indexPattern instance for each test + beforeEach(() => { + indexPattern = create('test-pattern'); + }); + + it('returns sorted object keys that combine _source, fields and metaFields in a defined order', () => { + const response = flattenHit( { - _id: 'hit-id-value', - _index: 'hit-index-value', - _type: 'hit-type-value', - _score: 77, - _source: { sourceTest: 123 }, - fields: { fieldTest: 123, invalidMapping: 345, nested: [{ field: 123 }] }, + _index: 'foobar', + _id: 'a', + _source: { + name: 'first', + }, + fields: { + date: ['1'], + zzz: ['z'], + _abc: ['a'], + }, }, - ], - }, - } as estypes.SearchResponse; - - it('converts fields by default', () => { - const table = tabifyDocs(response, index); - expect(table).toMatchSnapshot(); + indexPattern + ); + const expectedOrder = ['_abc', 'date', 'name', 'zzz', '_id', '_routing', '_score', '_type']; + expect(Object.keys(response)).toEqual(expectedOrder); + expect(Object.entries(response).map(([key]) => key)).toEqual(expectedOrder); + }); }); - it('converts source if option is set', () => { - const table = tabifyDocs(response, index, { source: true }); - expect(table).toMatchSnapshot(); - }); + describe('tabifyDocs', () => { + const fieldFormats = { + getInstance: (id: string) => ({ toJSON: () => ({ id }) }), + getDefaultInstance: (id: string) => ({ toJSON: () => ({ id }) }), + }; - it('skips nested fields if option is set', () => { - const table = tabifyDocs(response, index, { shallow: true }); - expect(table).toMatchSnapshot(); - }); + const index = new IndexPattern({ + spec: { + id: 'test-index', + fields: { + sourceTest: { name: 'sourceTest', type: 'number', searchable: true, aggregatable: true }, + fieldTest: { name: 'fieldTest', type: 'number', searchable: true, aggregatable: true }, + 'nested.field': { + name: 'nested.field', + type: 'number', + searchable: true, + aggregatable: true, + }, + }, + }, + metaFields: ['_id', '_index', '_score', '_type'], + fieldFormats: fieldFormats as any, + }); - it('combines meta fields if meta option is set', () => { - const table = tabifyDocs(response, index, { meta: true }); - expect(table).toMatchSnapshot(); - }); + // @ts-expect-error not full inteface + const response = { + hits: { + hits: [ + { + _id: 'hit-id-value', + _index: 'hit-index-value', + _type: 'hit-type-value', + _score: 77, + _source: { sourceTest: 123 }, + fields: { fieldTest: 123, invalidMapping: 345, nested: [{ field: 123 }] }, + }, + ], + }, + } as estypes.SearchResponse; + + it('converts fields by default', () => { + const table = tabifyDocs(response, index); + expect(table).toMatchSnapshot(); + }); + + it('converts source if option is set', () => { + const table = tabifyDocs(response, index, { source: true }); + expect(table).toMatchSnapshot(); + }); + + it('skips nested fields if option is set', () => { + const table = tabifyDocs(response, index, { shallow: true }); + expect(table).toMatchSnapshot(); + }); + + it('combines meta fields from index pattern', () => { + const table = tabifyDocs(response, index); + expect(table.columns.map((col) => col.id)).toEqual( + expect.arrayContaining(['_id', '_index', '_score', '_type']) + ); + }); - it('works without provided index pattern', () => { - const table = tabifyDocs(response); - expect(table).toMatchSnapshot(); + it('works without provided index pattern', () => { + const table = tabifyDocs(response); + expect(table).toMatchSnapshot(); + }); }); }); diff --git a/src/plugins/data/common/search/tabify/tabify_docs.ts b/src/plugins/data/common/search/tabify/tabify_docs.ts index 8e628e7741df5..4259488771761 100644 --- a/src/plugins/data/common/search/tabify/tabify_docs.ts +++ b/src/plugins/data/common/search/tabify/tabify_docs.ts @@ -11,12 +11,60 @@ import { isPlainObject } from 'lodash'; import { IndexPattern } from '../..'; import { Datatable, DatatableColumn, DatatableColumnType } from '../../../../expressions/common'; +type ValidMetaFieldNames = keyof Pick< + estypes.SearchHit, + | '_id' + | '_ignored' + | '_index' + | '_node' + | '_primary_term' + | '_routing' + | '_score' + | '_seq_no' + | '_shard' + | '_source' + | '_type' + | '_version' +>; +const VALID_META_FIELD_NAMES: ValidMetaFieldNames[] = [ + '_id', + '_ignored', + '_index', + '_node', + '_primary_term', + '_routing', + '_score', + '_seq_no', + '_shard', + '_source', + '_type', + '_version', +]; + +function isValidMetaFieldName(field: string): field is ValidMetaFieldNames { + // Since the array above is more narrowly typed than string[], we cannot use + // string to find a value in here. We manually cast it to wider string[] type + // so we're able to use `includes` on it. + return (VALID_META_FIELD_NAMES as string[]).includes(field); +} + export interface TabifyDocsOptions { shallow?: boolean; + /** + * If set to `false` the _source of the document, if requested, won't be + * merged into the flattened document. + */ source?: boolean; - meta?: boolean; } +/** + * Flattens an individual hit (from an ES response) into an object. This will + * create flattened field names, like `user.name`. + * + * @param hit The hit from an ES reponse's hits.hits[] + * @param indexPattern The index pattern for the requested index if available. + * @param params Parameters how to flatten the hit + */ export function flattenHit( hit: estypes.SearchHit, indexPattern?: IndexPattern, @@ -62,13 +110,36 @@ export function flattenHit( if (params?.source !== false && hit._source) { flatten(hit._source as Record); } - if (params?.meta !== false) { - // combine the fields that Discover allows to add as columns - const { _id, _index, _type, _score } = hit; - flatten({ _id, _index, _score, _type }); - } - return flat; + // Merge all valid meta fields into the flattened object + // expect for _source (in case that was specified as a meta field) + indexPattern?.metaFields?.forEach((metaFieldName) => { + if (!isValidMetaFieldName(metaFieldName) || metaFieldName === '_source') { + return; + } + flat[metaFieldName] = hit[metaFieldName]; + }); + + // Use a proxy to make sure that keys are always returned in a specific order, + // so we have a guarantee on the flattened order of keys. + return new Proxy(flat, { + ownKeys: (target) => { + return Reflect.ownKeys(target).sort((a, b) => { + const aIsMeta = indexPattern?.metaFields?.includes(String(a)); + const bIsMeta = indexPattern?.metaFields?.includes(String(b)); + if (aIsMeta && bIsMeta) { + return String(a).localeCompare(String(b)); + } + if (aIsMeta) { + return 1; + } + if (bIsMeta) { + return -1; + } + return String(a).localeCompare(String(b)); + }); + }, + }); } export const tabifyDocs = ( diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 3ae98c083976e..4d51a7ae0ad77 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -52,7 +52,6 @@ import { ILLEGAL_CHARACTERS_VISIBLE, ILLEGAL_CHARACTERS, validateDataView, - flattenHitWrapper, } from './data_views'; export type { IndexPatternsService } from './data_views'; @@ -69,7 +68,6 @@ export const indexPatterns = { getFieldSubtypeMulti, getFieldSubtypeNested, validate: validateDataView, - flattenHitWrapper, }; export { diff --git a/src/plugins/data_views/common/data_views/data_view.ts b/src/plugins/data_views/common/data_views/data_view.ts index 5768ebe635729..57db127208dc3 100644 --- a/src/plugins/data_views/common/data_views/data_view.ts +++ b/src/plugins/data_views/common/data_views/data_view.ts @@ -72,6 +72,9 @@ export class DataView implements IIndexPattern { formatField: FormatFieldFn; }; public formatField: FormatFieldFn; + /** + * @deprecated Use `flattenHit` utility method exported from data plugin instead. + */ public flattenHit: (hit: Record, deep?: boolean) => Record; public metaFields: string[]; /** diff --git a/src/plugins/data_views/common/data_views/flatten_hit.ts b/src/plugins/data_views/common/data_views/flatten_hit.ts index ddf484affa298..0a6388f0914b1 100644 --- a/src/plugins/data_views/common/data_views/flatten_hit.ts +++ b/src/plugins/data_views/common/data_views/flatten_hit.ts @@ -6,6 +6,11 @@ * Side Public License, v 1. */ +// --------- DEPRECATED --------- +// This implementation of flattenHit is deprecated and should no longer be used. +// If you consider adding features to this, please don't but use the `flattenHit` +// implementation from the data plugin. + import _ from 'lodash'; import { DataView } from './data_view'; @@ -114,15 +119,3 @@ export function flattenHitWrapper(dataView: DataView, metaFields = {}, cache = n return decorateFlattened(flattened); }; } - -/** - * This wraps `flattenHitWrapper` so one single cache can be provided for all uses of that - * function. The returned value of this function is what is included in the index patterns - * setup contract. - * - * @public - */ -export function createFlattenHitWrapper() { - const cache = new WeakMap(); - return _.partial(flattenHitWrapper, _, _, cache); -} diff --git a/src/plugins/data_views/public/index.ts b/src/plugins/data_views/public/index.ts index 572806df11fa3..5c810ec1fd4c8 100644 --- a/src/plugins/data_views/public/index.ts +++ b/src/plugins/data_views/public/index.ts @@ -13,7 +13,7 @@ export { ILLEGAL_CHARACTERS, validateDataView, } from '../common/lib'; -export { flattenHitWrapper, formatHitProvider, onRedirectNoIndexPattern } from './data_views'; +export { formatHitProvider, onRedirectNoIndexPattern } from './data_views'; export { IndexPatternField, IIndexPatternFieldList, TypeMeta } from '../common'; diff --git a/src/plugins/discover/public/__mocks__/index_pattern.ts b/src/plugins/discover/public/__mocks__/index_pattern.ts index f9cc202f9063e..2acb512617a6b 100644 --- a/src/plugins/discover/public/__mocks__/index_pattern.ts +++ b/src/plugins/discover/public/__mocks__/index_pattern.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { IIndexPatternFieldList } from '../../../data/common'; +import type { estypes } from '@elastic/elasticsearch'; +import { flattenHit, IIndexPatternFieldList } from '../../../data/common'; import { IndexPattern } from '../../../data/common'; -import { indexPatterns } from '../../../data/public'; const fields = [ { @@ -85,10 +85,11 @@ const indexPattern = { getFormatterForField: () => ({ convert: () => 'formatted' }), } as unknown as IndexPattern; -indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); indexPattern.isTimeBased = () => !!indexPattern.timeFieldName; indexPattern.formatField = (hit: Record, fieldName: string) => { - return fieldName === '_source' ? hit._source : indexPattern.flattenHit(hit)[fieldName]; + return fieldName === '_source' + ? hit._source + : flattenHit(hit as unknown as estypes.SearchHit, indexPattern)[fieldName]; }; export const indexPatternMock = indexPattern; diff --git a/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts b/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts index 0f64a6c67741d..6cf8e8b3485ff 100644 --- a/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts +++ b/src/plugins/discover/public/__mocks__/index_pattern_with_timefield.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { IIndexPatternFieldList } from '../../../data/common'; +import { flattenHit, IIndexPatternFieldList } from '../../../data/common'; import { IndexPattern } from '../../../data/common'; -import { indexPatterns } from '../../../data/public'; +import type { estypes } from '@elastic/elasticsearch'; const fields = [ { @@ -76,10 +76,11 @@ const indexPattern = { popularizeField: () => {}, } as unknown as IndexPattern; -indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); indexPattern.isTimeBased = () => !!indexPattern.timeFieldName; indexPattern.formatField = (hit: Record, fieldName: string) => { - return fieldName === '_source' ? hit._source : indexPattern.flattenHit(hit)[fieldName]; + return fieldName === '_source' + ? hit._source + : flattenHit(hit as unknown as estypes.SearchHit, indexPattern)[fieldName]; }; export const indexPatternWithTimefieldMock = indexPattern; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx index 8d56f2adeaf65..d91735460af08 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx @@ -10,6 +10,7 @@ import React, { Fragment, useCallback, useMemo, useState } from 'react'; import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiIcon } from '@elastic/eui'; +import { flattenHit } from '../../../../../../../../data/common'; import { DocViewer } from '../../../../../components/doc_viewer/doc_viewer'; import { FilterManager, IndexPattern } from '../../../../../../../../data/public'; import { TableCell } from './table_row/table_cell'; @@ -57,7 +58,7 @@ export const TableRow = ({ }); const anchorDocTableRowSubj = row.isAnchor ? ' docTableAnchorRow' : ''; - const flattenedRow = useMemo(() => indexPattern.flattenHit(row), [indexPattern, row]); + const flattenedRow = useMemo(() => flattenHit(row, indexPattern), [indexPattern, row]); const mapping = useMemo(() => indexPattern.fields.getByName, [indexPattern]); // toggle display of the rows details, a full list of the fields from each row diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.test.tsx index e53bf006e2b4e..a550dbd59b9fa 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar.test.tsx @@ -15,7 +15,7 @@ import realHits from '../../../../../__fixtures__/real_hits.js'; import { mountWithIntl } from '@kbn/test/jest'; import React from 'react'; import { DiscoverSidebarProps } from './discover_sidebar'; -import { IndexPatternAttributes } from '../../../../../../../data/common'; +import { flattenHit, IndexPatternAttributes } from '../../../../../../../data/common'; import { SavedObject } from '../../../../../../../../core/types'; import { getDefaultFieldFilter } from './lib/field_filter'; import { DiscoverSidebarComponent as DiscoverSidebar } from './discover_sidebar'; @@ -44,7 +44,7 @@ function getCompProps(): DiscoverSidebarProps { const fieldCounts: Record = {}; for (const hit of hits) { - for (const key of Object.keys(indexPattern.flattenHit(hit))) { + for (const key of Object.keys(flattenHit(hit, indexPattern))) { fieldCounts[key] = (fieldCounts[key] || 0) + 1; } } diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar_responsive.test.tsx index 9d73f885c988d..ded7897d2a9e5 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar_responsive.test.tsx +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_sidebar_responsive.test.tsx @@ -15,7 +15,7 @@ import realHits from '../../../../../__fixtures__/real_hits.js'; import { act } from 'react-dom/test-utils'; import { mountWithIntl } from '@kbn/test/jest'; import React from 'react'; -import { IndexPatternAttributes } from '../../../../../../../data/common'; +import { flattenHit, IndexPatternAttributes } from '../../../../../../../data/common'; import { SavedObject } from '../../../../../../../../core/types'; import { DiscoverSidebarResponsive, @@ -72,7 +72,7 @@ function getCompProps(): DiscoverSidebarResponsiveProps { const indexPattern = stubLogstashIndexPattern; // @ts-expect-error _.each() is passing additional args to flattenHit - const hits = each(cloneDeep(realHits), indexPattern.flattenHit) as Array< + const hits = each(cloneDeep(realHits), (hit) => flattenHit(hit, indexPattern)) as Array< Record > as ElasticSearchHit[]; @@ -83,7 +83,7 @@ function getCompProps(): DiscoverSidebarResponsiveProps { ]; for (const hit of hits) { - for (const key of Object.keys(indexPattern.flattenHit(hit))) { + for (const key of Object.keys(flattenHit(hit, indexPattern))) { mockfieldCounts[key] = (mockfieldCounts[key] || 0) + 1; } } diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.js b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.js index 8f86cdad82cf7..be7e9c616273d 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.js +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.js @@ -8,12 +8,12 @@ import { map, sortBy, without, each, defaults, isObject } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { flattenHit } from '../../../../../../../../data/common'; function getFieldValues(hits, field, indexPattern) { const name = field.name; - const flattenHit = indexPattern.flattenHit; return map(hits, function (hit) { - return flattenHit(hit)[name]; + return flattenHit(hit, indexPattern)[name]; }); } diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.test.ts b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.test.ts index c3ff7970c5aac..d4bc41f36b2d4 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/field_calculator.test.ts @@ -13,6 +13,7 @@ import { keys, each, cloneDeep, clone, uniq, filter, map } from 'lodash'; import realHits from '../../../../../../__fixtures__/real_hits.js'; import { IndexPattern } from '../../../../../../../../data/public'; +import { flattenHit } from '../../../../../../../../data/common'; // @ts-expect-error import { fieldCalculator } from './field_calculator'; @@ -120,7 +121,7 @@ describe('fieldCalculator', function () { let hits: any; beforeEach(function () { - hits = each(cloneDeep(realHits), (hit) => indexPattern.flattenHit(hit)); + hits = each(cloneDeep(realHits), (hit) => flattenHit(hit, indexPattern)); }); it('Should return an array of values for _source fields', function () { diff --git a/src/plugins/discover/public/application/apps/main/utils/calc_field_counts.ts b/src/plugins/discover/public/application/apps/main/utils/calc_field_counts.ts index 1ce7023539be4..211c4e5c8b069 100644 --- a/src/plugins/discover/public/application/apps/main/utils/calc_field_counts.ts +++ b/src/plugins/discover/public/application/apps/main/utils/calc_field_counts.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { IndexPattern } from 'src/plugins/data/common'; +import { flattenHit, IndexPattern } from '../../../../../../data/common'; import { ElasticSearchHit } from '../../../doc_views/doc_views_types'; /** @@ -22,7 +22,7 @@ export function calcFieldCounts( return {}; } for (const hit of rows) { - const fields = Object.keys(indexPattern.flattenHit(hit)); + const fields = Object.keys(flattenHit(hit, indexPattern)); for (const fieldName of fields) { counts[fieldName] = (counts[fieldName] || 0) + 1; } diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index 0fe506b3b8537..11323080274a9 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -21,7 +21,7 @@ import { EuiLoadingSpinner, EuiIcon, } from '@elastic/eui'; -import type { IndexPattern } from 'src/plugins/data/common'; +import { flattenHit, IndexPattern } from '../../../../../data/common'; import { DocViewFilterFn, ElasticSearchHit } from '../../doc_views/doc_views_types'; import { getSchemaDetectors } from './discover_grid_schema'; import { DiscoverGridFlyout } from './discover_grid_flyout'; @@ -271,7 +271,7 @@ export const DiscoverGrid = ({ getRenderCellValueFn( indexPattern, displayedRows, - displayedRows ? displayedRows.map((hit) => indexPattern.flattenHit(hit)) : [], + displayedRows ? displayedRows.map((hit) => flattenHit(hit, indexPattern)) : [], useNewFieldsApi, fieldsToShow, services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED) diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx index b1823eb3d668c..a31b551821ddb 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_cell_actions.tsx @@ -9,7 +9,7 @@ import React, { useContext } from 'react'; import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IndexPatternField } from '../../../../../data/common'; +import { flattenHit, IndexPatternField } from '../../../../../data/common'; import { DiscoverGridContext } from './discover_grid_context'; export const FilterInBtn = ({ @@ -27,7 +27,7 @@ export const FilterInBtn = ({ { const row = context.rows[rowIndex]; - const flattened = context.indexPattern.flattenHit(row); + const flattened = flattenHit(row, context.indexPattern); if (flattened) { context.onFilter(columnId, flattened[columnId], '+'); @@ -60,7 +60,7 @@ export const FilterOutBtn = ({ { const row = context.rows[rowIndex]; - const flattened = context.indexPattern.flattenHit(row); + const flattened = flattenHit(row, context.indexPattern); if (flattened) { context.onFilter(columnId, flattened[columnId], '-'); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx index 41cf3f5a68edb..e9b93e21553a2 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx @@ -17,6 +17,17 @@ import { esHits } from '../../../__mocks__/es_hits'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; import { DiscoverGridContext } from './discover_grid_context'; +const baseContextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), +}; + describe('document selection', () => { describe('getDocId', () => { test('doc with custom routing', () => { @@ -39,14 +50,7 @@ describe('document selection', () => { describe('SelectButton', () => { test('is not checked', () => { const contextMock = { - expanded: undefined, - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), + ...baseContextMock, }; const component = mountWithIntl( @@ -68,14 +72,8 @@ describe('document selection', () => { test('is checked', () => { const contextMock = { - expanded: undefined, - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, + ...baseContextMock, selectedDocs: ['i::1::'], - setSelectedDocs: jest.fn(), }; const component = mountWithIntl( @@ -97,14 +95,7 @@ describe('document selection', () => { test('adding a selection', () => { const contextMock = { - expanded: undefined, - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), + ...baseContextMock, }; const component = mountWithIntl( @@ -126,14 +117,8 @@ describe('document selection', () => { }); test('removing a selection', () => { const contextMock = { - expanded: undefined, - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, + ...baseContextMock, selectedDocs: ['i::1::'], - setSelectedDocs: jest.fn(), }; const component = mountWithIntl( diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx index d1299b39a25b2..3f7cb70091cfa 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx @@ -14,17 +14,21 @@ import { DiscoverGridContext } from './discover_grid_context'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; import { esHits } from '../../../__mocks__/es_hits'; +const baseContextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), +}; + describe('Discover grid view button ', function () { it('when no document is expanded, setExpanded is called with current document', async () => { const contextMock = { - expanded: undefined, - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), + ...baseContextMock, }; const component = mountWithIntl( @@ -45,14 +49,8 @@ describe('Discover grid view button ', function () { }); it('when the current document is expanded, setExpanded is called with undefined', async () => { const contextMock = { + ...baseContextMock, expanded: esHits[0], - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), }; const component = mountWithIntl( @@ -73,14 +71,8 @@ describe('Discover grid view button ', function () { }); it('when another document is expanded, setExpanded is called with the current document', async () => { const contextMock = { + ...baseContextMock, expanded: esHits[0], - setExpanded: jest.fn(), - rows: esHits, - onFilter: jest.fn(), - indexPattern: indexPatternMock, - isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), }; const component = mountWithIntl( diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx index 5aca237d46581..6556876217953 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.test.tsx @@ -11,6 +11,7 @@ import { ReactWrapper, shallow } from 'enzyme'; import { getRenderCellValueFn } from './get_render_cell_value'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { flattenHit } from 'src/plugins/data/common'; jest.mock('../../../../../kibana_react/public', () => ({ useUiSetting: () => true, @@ -68,12 +69,16 @@ const rowsFieldsWithTopLevelObject: ElasticSearchHit[] = [ }, ]; +const flatten = (hit: ElasticSearchHit): Record => { + return flattenHit(hit, indexPatternMock); +}; + describe('Discover grid cell rendering', function () { it('renders bytes column correctly', () => { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsSource, - rowsSource.map((row) => indexPatternMock.flattenHit(row)), + rowsSource.map(flatten), false, [], 100 @@ -95,7 +100,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsSource, - rowsSource.map((row) => indexPatternMock.flattenHit(row)), + rowsSource.map(flatten), false, [], 100 @@ -146,7 +151,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsSource, - rowsSource.map((row) => indexPatternMock.flattenHit(row)), + rowsSource.map(flatten), false, [], 100 @@ -189,7 +194,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFields, - rowsFields.map((row) => indexPatternMock.flattenHit(row)), + rowsFields.map(flatten), true, [], 100 @@ -244,7 +249,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFields, - rowsFields.map((row) => indexPatternMock.flattenHit(row)), + rowsFields.map(flatten), true, [], // this is the number of rendered items @@ -287,7 +292,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFields, - rowsFields.map((row) => indexPatternMock.flattenHit(row)), + rowsFields.map(flatten), true, [], 100 @@ -335,7 +340,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFieldsWithTopLevelObject, - rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)), + rowsFieldsWithTopLevelObject.map(flatten), true, [], 100 @@ -376,7 +381,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFieldsWithTopLevelObject, - rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)), + rowsFieldsWithTopLevelObject.map(flatten), true, [], 100 @@ -416,7 +421,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFieldsWithTopLevelObject, - rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)), + rowsFieldsWithTopLevelObject.map(flatten), true, [], 100 @@ -447,7 +452,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsFieldsWithTopLevelObject, - rowsFieldsWithTopLevelObject.map((row) => indexPatternMock.flattenHit(row)), + rowsFieldsWithTopLevelObject.map(flatten), true, [], 100 @@ -466,7 +471,9 @@ describe('Discover grid cell rendering', function () { @@ -477,7 +484,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsSource, - rowsSource.map((row) => indexPatternMock.flattenHit(row)), + rowsSource.map(flatten), false, [], 100 @@ -499,7 +506,7 @@ describe('Discover grid cell rendering', function () { const DiscoverGridCellValue = getRenderCellValueFn( indexPatternMock, rowsSource, - rowsSource.map((row) => indexPatternMock.flattenHit(row)), + rowsSource.map(flatten), false, [], 100 diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 3f010d9d07737..ce914edcec703 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test/jest'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewerTable, DocViewerTableProps } from './table'; -import { indexPatterns, IndexPattern } from '../../../../../data/public'; +import { IndexPattern } from '../../../../../data/public'; import { ElasticSearchHit } from '../../doc_views/doc_views_types'; jest.mock('../../../kibana_services', () => ({ @@ -65,7 +65,7 @@ const indexPattern = { ], }, metaFields: ['_index', '_score'], - flattenHit: undefined, + flattenHit: jest.fn(), formatHit: jest.fn((hit) => hit._source), } as unknown as IndexPattern; @@ -73,8 +73,6 @@ indexPattern.fields.getByName = (name: string) => { return indexPattern.fields.getAll().find((field) => field.name === name); }; -indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); - const mountComponent = (props: DocViewerTableProps) => { return mountWithIntl(); }; diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index eab3ba6e3d29a..7f597d846f88f 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react'; import { EuiInMemoryTable } from '@elastic/eui'; import { IndexPattern, IndexPatternField } from '../../../../../data/public'; +import { flattenHit } from '../../../../../data/common'; import { SHOW_MULTIFIELDS } from '../../../../common'; import { getServices } from '../../../kibana_services'; import { isNestedFieldParent } from '../../apps/main/utils/nested_fields'; @@ -95,7 +96,7 @@ export const DocViewerTable = ({ return null; } - const flattened = indexPattern?.flattenHit(hit); + const flattened = flattenHit(hit, indexPattern, { source: true }); const fieldsToShow = getFieldsToShow(Object.keys(flattened), indexPattern, showMultiFields); const items: FieldRecord[] = Object.keys(flattened) diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index f393661e4c490..1902c4ed0272e 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -75,6 +75,7 @@ const mockSearchSourceGetFieldDefault = jest.fn().mockImplementation((key: strin getByName: jest.fn().mockImplementation(() => []), getByType: jest.fn().mockImplementation(() => []), }, + metaFields: ['_id', '_index', '_type', '_score'], getFormatterForField: jest.fn(), }; } diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index c269677ae930d..6c2989d54309d 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -356,7 +356,7 @@ export class CsvGenerator { let table: Datatable | undefined; try { - table = tabifyDocs(results, index, { shallow: true, meta: true }); + table = tabifyDocs(results, index, { shallow: true }); } catch (err) { this.logger.error(err); } From f52c718f112bdba3320ac4591a7d3098082f7014 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Thu, 14 Oct 2021 09:07:22 -0400 Subject: [PATCH 18/31] fix cloudwatch category assignmet (#114928) --- src/plugins/home/server/tutorials/cloudwatch_logs/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts b/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts index dd035a66c5ced..cf0c27ed9be73 100644 --- a/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts +++ b/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts @@ -53,6 +53,6 @@ export function cloudwatchLogsSpecProvider(context: TutorialContext): TutorialSc onPrem: onPremInstructions([], context), elasticCloud: cloudInstructions(), onPremElasticCloud: onPremCloudInstructions(), - integrationBrowserCategories: ['security', 'network', 'web'], + integrationBrowserCategories: ['aws', 'cloud', 'datastore', 'security', 'network'], }; } From 4db243703649d955500208a4ca89690b52e5fc23 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 14 Oct 2021 15:28:40 +0200 Subject: [PATCH 19/31] [Lens] Thresholds: when computing default static value take into account all layer metrics (#113647) * :sparkles: compute the default threshold based on data bounds * :bug: Fix multi layer types issue * :white_check_mark: Fix test * :white_check_mark: Fix other test * :bug: Fix computation bug for the initial static value * :white_check_mark: Add new suite of test for static value computation * :bug: Fix extents bug and refactor in a single function + tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/xy_visualization/expression.tsx | 30 +- .../public/xy_visualization/state_helpers.ts | 4 + .../threshold_helpers.test.ts | 569 ++++++++++++++++++ .../xy_visualization/threshold_helpers.tsx | 146 +++-- 4 files changed, 700 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugins/lens/public/xy_visualization/threshold_helpers.test.ts diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 87462e71f3cf6..7aee537ebbedd 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -67,6 +67,7 @@ import { getThresholdRequiredPaddings, ThresholdAnnotations, } from './expression_thresholds'; +import { computeOverallDataDomain } from './threshold_helpers'; declare global { interface Window { @@ -250,6 +251,10 @@ export function XYChart({ const chartBaseTheme = chartsThemeService.useChartsBaseTheme(); const darkMode = chartsThemeService.useDarkMode(); const filteredLayers = getFilteredLayers(layers, data); + const layersById = filteredLayers.reduce((memo, layer) => { + memo[layer.layerId] = layer; + return memo; + }, {} as Record); const handleCursorUpdate = useActiveCursor(chartsActiveCursorService, chartRef, { datatables: Object.values(data.tables), @@ -386,7 +391,7 @@ export function XYChart({ const extent = axis.groupId === 'left' ? yLeftExtent : yRightExtent; const hasBarOrArea = Boolean( axis.series.some((series) => { - const seriesType = filteredLayers.find((l) => l.layerId === series.layer)?.seriesType; + const seriesType = layersById[series.layer]?.seriesType; return seriesType?.includes('bar') || seriesType?.includes('area'); }) ); @@ -406,20 +411,15 @@ export function XYChart({ ); if (!fit && axisHasThreshold) { // Remove this once the chart will support automatic annotation fit for other type of charts - for (const series of axis.series) { - const table = data.tables[series.layer]; - for (const row of table.rows) { - for (const column of table.columns) { - if (column.id === series.accessor) { - const value = row[column.id]; - if (typeof value === 'number') { - // keep the 0 in view - max = Math.max(value, max || 0, 0); - min = Math.min(value, min || 0, 0); - } - } - } - } + const { min: computedMin, max: computedMax } = computeOverallDataDomain( + filteredLayers, + axis.series.map(({ accessor }) => accessor), + data.tables + ); + + if (computedMin != null && computedMax != null) { + max = Math.max(computedMax, max || 0); + min = Math.min(computedMin, min || 0); } for (const { layerId, yConfig } of thresholdLayers) { const table = data.tables[layerId]; diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index 4edf7fdf5e512..14a82011cb526 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -26,6 +26,10 @@ export function isPercentageSeries(seriesType: SeriesType) { ); } +export function isStackedChart(seriesType: SeriesType) { + return seriesType.includes('stacked'); +} + export function isHorizontalChart(layers: Array<{ seriesType: SeriesType }>) { return layers.every((l) => isHorizontalSeries(l.seriesType)); } diff --git a/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.test.ts b/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.test.ts new file mode 100644 index 0000000000000..d7286de0316d6 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.test.ts @@ -0,0 +1,569 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { XYLayerConfig } from '../../common/expressions'; +import { FramePublicAPI } from '../types'; +import { computeOverallDataDomain, getStaticValue } from './threshold_helpers'; + +function getActiveData(json: Array<{ id: string; rows: Array> }>) { + return json.reduce((memo, { id, rows }) => { + const columns = Object.keys(rows[0]).map((columnId) => ({ + id: columnId, + name: columnId, + meta: { type: 'number' as const }, + })); + memo[id] = { + type: 'datatable' as const, + columns, + rows, + }; + return memo; + }, {} as NonNullable); +} + +describe('threshold helpers', () => { + describe('getStaticValue', () => { + const hasDateHistogram = () => false; + const hasAllNumberHistogram = () => true; + + it('should return fallback value on missing data', () => { + expect(getStaticValue([], 'x', {}, hasAllNumberHistogram)).toBe(100); + }); + + it('should return fallback value on no-configuration/missing hit on current data', () => { + // no-config: missing layer + expect( + getStaticValue( + [], + 'x', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(100); + // accessor id has no hit in data + expect( + getStaticValue( + [{ layerId: 'id-a', seriesType: 'area' } as XYLayerConfig], // missing xAccessor for groupId == x + 'x', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(100); + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['d'], + } as XYLayerConfig, + ], // missing hit of accessor "d" in data + 'yLeft', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(100); + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a'], + } as XYLayerConfig, + ], // missing yConfig fallbacks to left axis, but the requested group is yRight + 'yRight', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(100); + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a'], + } as XYLayerConfig, + ], // same as above with x groupId + 'x', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(100); + }); + + it('should work for no yConfig defined and fallback to left axis', () => { + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a'], + } as XYLayerConfig, + ], + 'yLeft', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(75); // 3/4 of "a" only + }); + + it('should extract axis side from yConfig', () => { + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a'], + yConfig: [{ forAccessor: 'a', axisMode: 'right' }], + } as XYLayerConfig, + ], + 'yRight', + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(75); // 3/4 of "a" only + }); + + it('should correctly distribute axis on left and right with different formatters when in auto', () => { + const tables = getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 200, c: 100 }) }, + ]); + tables['id-a'].columns[0].meta.params = { id: 'number' }; // a: number formatter + tables['id-a'].columns[1].meta.params = { id: 'percent' }; // b: percent formatter + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a', 'b'], + } as XYLayerConfig, + ], + 'yLeft', + { activeData: tables }, + hasAllNumberHistogram + ) + ).toBe(75); // 3/4 of "a" only + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a', 'b'], + } as XYLayerConfig, + ], + 'yRight', + { activeData: tables }, + hasAllNumberHistogram + ) + ).toBe(150); // 3/4 of "b" only + }); + + it('should ignore hasHistogram for left or right axis', () => { + const tables = getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 200, c: 100 }) }, + ]); + tables['id-a'].columns[0].meta.params = { id: 'number' }; // a: number formatter + tables['id-a'].columns[1].meta.params = { id: 'percent' }; // b: percent formatter + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a', 'b'], + } as XYLayerConfig, + ], + 'yLeft', + { activeData: tables }, + hasDateHistogram + ) + ).toBe(75); // 3/4 of "a" only + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + accessors: ['a', 'b'], + } as XYLayerConfig, + ], + 'yRight', + { activeData: tables }, + hasDateHistogram + ) + ).toBe(150); // 3/4 of "b" only + }); + + it('should early exit for x group if a date histogram is detected', () => { + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + xAccessor: 'a', + accessors: [], + } as XYLayerConfig, + ], + 'x', // this is influenced by the callback + { + activeData: getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + ]), + }, + hasDateHistogram + ) + ).toBe(100); + }); + + it('should not force zero-based interval for x group', () => { + expect( + getStaticValue( + [ + { + layerId: 'id-a', + seriesType: 'area', + layerType: 'data', + xAccessor: 'a', + accessors: [], + } as XYLayerConfig, + ], + 'x', + { + activeData: getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ a: i % 2 ? 33 : 50 })), + }, + ]), + }, + hasAllNumberHistogram + ) + ).toBe(45.75); // 33 (min) + (50 - 33) * 3/4 + }); + }); + + describe('computeOverallDataDomain', () => { + it('should compute the correct value for a single layer with stacked series', () => { + for (const seriesType of ['bar_stacked', 'bar_horizontal_stacked', 'area_stacked']) + expect( + computeOverallDataDomain( + [{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] } as XYLayerConfig], + ['a', 'b', 'c'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ + a: i === 0 ? 25 : null, + b: i === 1 ? 50 : null, + c: i === 2 ? 75 : null, + })), + }, + ]) + ) + ).toEqual({ min: 0, max: 150 }); // there's just one series with 150, so the lowerbound fallbacks to 0 + }); + + it('should work for percentage series', () => { + for (const seriesType of [ + 'bar_percentage_stacked', + 'bar_horizontal_percentage_stacked', + 'area_percentage_stacked', + ]) + expect( + computeOverallDataDomain( + [{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] } as XYLayerConfig], + ['a', 'b', 'c'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ + a: i === 0 ? 0.25 : null, + b: i === 1 ? 0.25 : null, + c: i === 2 ? 0.25 : null, + })), + }, + ]) + ) + ).toEqual({ min: 0, max: 0.75 }); + }); + + it('should compute the correct value for multiple layers with stacked series', () => { + for (const seriesType of ['bar_stacked', 'bar_horizontal_stacked', 'area_stacked']) { + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] }, + { layerId: 'id-b', seriesType, accessors: ['d', 'e', 'f'] }, + ] as XYLayerConfig[], + ['a', 'b', 'c', 'd', 'e', 'f'], + getActiveData([ + { id: 'id-a', rows: [{ a: 25, b: 100, c: 100 }] }, + { id: 'id-b', rows: [{ d: 50, e: 50, f: 50 }] }, + ]) + ) + ).toEqual({ min: 0, max: 375 }); + // same as before but spread on 3 rows with nulls + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] }, + { layerId: 'id-b', seriesType, accessors: ['d', 'e', 'f'] }, + ] as XYLayerConfig[], + ['a', 'b', 'c', 'd', 'e', 'f'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ + a: i === 0 ? 25 : null, + b: i === 1 ? 100 : null, + c: i === 2 ? 100 : null, + })), + }, + { + id: 'id-b', + rows: Array(3) + .fill(1) + .map((_, i) => ({ + d: i === 0 ? 50 : null, + e: i === 1 ? 50 : null, + f: i === 2 ? 50 : null, + })), + }, + ]) + ) + ).toEqual({ min: 0, max: 375 }); + } + }); + + it('should compute the correct value for multiple layers with non-stacked series', () => { + for (const seriesType of ['bar', 'bar_horizontal', 'line', 'area']) + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] }, + { layerId: 'id-b', seriesType, accessors: ['d', 'e', 'f'] }, + ] as XYLayerConfig[], + ['a', 'b', 'c', 'd', 'e', 'f'], + getActiveData([ + { id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }, + { id: 'id-b', rows: Array(3).fill({ d: 50, e: 50, f: 50 }) }, + ]) + ) + ).toEqual({ min: 50, max: 100 }); + }); + + it('should compute the correct value for mixed series (stacked + non-stacked)', () => { + for (const nonStackedSeries of ['bar', 'bar_horizontal', 'line', 'area']) { + for (const stackedSeries of ['bar_stacked', 'bar_horizontal_stacked', 'area_stacked']) { + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType: nonStackedSeries, accessors: ['a', 'b', 'c'] }, + { layerId: 'id-b', seriesType: stackedSeries, accessors: ['d', 'e', 'f'] }, + ] as XYLayerConfig[], + ['a', 'b', 'c', 'd', 'e', 'f'], + getActiveData([ + { id: 'id-a', rows: [{ a: 100, b: 100, c: 100 }] }, + { id: 'id-b', rows: [{ d: 50, e: 50, f: 50 }] }, + ]) + ) + ).toEqual({ + min: 0, // min is 0 as there is at least one stacked series + max: 150, // max is id-b layer accessor sum + }); + } + } + }); + + it('should compute the correct value for a histogram stacked chart', () => { + for (const seriesType of ['bar_stacked', 'bar_horizontal_stacked', 'area_stacked']) + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType, xAccessor: 'c', accessors: ['a', 'b'] }, + { layerId: 'id-b', seriesType, xAccessor: 'f', accessors: ['d', 'e'] }, + ] as XYLayerConfig[], + ['a', 'b', 'd', 'e'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ a: 50 * i, b: 100 * i, c: i })), + }, + { + id: 'id-b', + rows: Array(3) + .fill(1) + .map((_, i) => ({ d: 25 * (i + 1), e: i % 2 ? 100 : null, f: i })), + }, + ]) + ) + ).toEqual({ min: 0, max: 375 }); + }); + + it('should compute the correct value for a histogram non-stacked chart', () => { + for (const seriesType of ['bar', 'bar_horizontal', 'line', 'area']) + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType, xAccessor: 'c', accessors: ['a', 'b'] }, + { layerId: 'id-b', seriesType, xAccessor: 'f', accessors: ['d', 'e'] }, + ] as XYLayerConfig[], + ['a', 'b', 'd', 'e'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ a: 50 * i, b: 100 * i, c: i })), + }, + { + id: 'id-b', + rows: Array(3) + .fill(1) + .map((_, i) => ({ d: 25 * (i + 1), e: i % 2 ? 100 : null, f: i })), + }, + ]) + ) + ).toEqual({ min: 0, max: 200 }); + }); + + it('should compute the result taking into consideration negative-based intervals too', () => { + // stacked + expect( + computeOverallDataDomain( + [ + { + layerId: 'id-a', + seriesType: 'area_stacked', + accessors: ['a', 'b', 'c'], + } as XYLayerConfig, + ], + ['a', 'b', 'c'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ + a: i === 0 ? -100 : null, + b: i === 1 ? 200 : null, + c: i === 2 ? 100 : null, + })), + }, + ]) + ) + ).toEqual({ min: 0, max: 200 }); // it is stacked, so max is the sum and 0 is the fallback + expect( + computeOverallDataDomain( + [{ layerId: 'id-a', seriesType: 'area', accessors: ['a', 'b', 'c'] } as XYLayerConfig], + ['a', 'b', 'c'], + getActiveData([ + { + id: 'id-a', + rows: Array(3) + .fill(1) + .map((_, i) => ({ + a: i === 0 ? -100 : null, + b: i === 1 ? 200 : null, + c: i === 2 ? 100 : null, + })), + }, + ]) + ) + ).toEqual({ min: -100, max: 200 }); + }); + + it('should return no result if no layers or accessors are passed', () => { + expect( + computeOverallDataDomain( + [], + ['a', 'b', 'c'], + getActiveData([{ id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) }]) + ) + ).toEqual({ min: undefined, max: undefined }); + }); + + it('should return no result if data or table is not available', () => { + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType: 'area', accessors: ['a', 'b', 'c'] }, + { layerId: 'id-b', seriesType: 'line', accessors: ['d', 'e', 'f'] }, + ] as XYLayerConfig[], + ['a', 'b'], + getActiveData([{ id: 'id-c', rows: [{ a: 100, b: 100 }] }]) // mind the layer id here + ) + ).toEqual({ min: undefined, max: undefined }); + + expect( + computeOverallDataDomain( + [ + { layerId: 'id-a', seriesType: 'bar', accessors: ['a', 'b', 'c'] }, + { layerId: 'id-b', seriesType: 'bar_stacked' }, + ] as XYLayerConfig[], + ['a', 'b'], + getActiveData([]) + ) + ).toEqual({ min: undefined, max: undefined }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx index ec47350709473..8bf5f84b15bad 100644 --- a/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx @@ -5,12 +5,13 @@ * 2.0. */ +import { partition } from 'lodash'; import { layerTypes } from '../../common'; import type { XYLayerConfig, YConfig } from '../../common/expressions'; import { Datatable } from '../../../../../src/plugins/expressions/public'; import type { DatasourcePublicAPI, FramePublicAPI } from '../types'; import { groupAxesByType } from './axes_configuration'; -import { isPercentageSeries } from './state_helpers'; +import { isPercentageSeries, isStackedChart } from './state_helpers'; import type { XYState } from './types'; import { checkScaleOperation } from './visualization_helpers'; @@ -91,14 +92,22 @@ export function getStaticValue( // filter and organize data dimensions into threshold groups // now pick the columnId in the active data - const { dataLayer, accessor } = getAccessorCriteriaForGroup(groupId, dataLayers, activeData); - if (groupId === 'x' && dataLayer && !layerHasNumberHistogram(dataLayer)) { + const { + dataLayers: filteredLayers, + untouchedDataLayers, + accessors, + } = getAccessorCriteriaForGroup(groupId, dataLayers, activeData); + if ( + groupId === 'x' && + filteredLayers.length && + !untouchedDataLayers.some(layerHasNumberHistogram) + ) { return fallbackValue; } return ( computeStaticValueForGroup( - dataLayer, - accessor, + filteredLayers, + accessors, activeData, groupId !== 'x' // histogram axis should compute the min based on the current data ) || fallbackValue @@ -111,54 +120,123 @@ function getAccessorCriteriaForGroup( activeData: FramePublicAPI['activeData'] ) { switch (groupId) { - case 'x': - const dataLayer = dataLayers.find(({ xAccessor }) => xAccessor); + case 'x': { + const filteredDataLayers = dataLayers.filter(({ xAccessor }) => xAccessor); + // need to reshape the dataLayers to match the other accessors format return { - dataLayer, - accessor: dataLayer?.xAccessor, + dataLayers: filteredDataLayers.map(({ accessors, xAccessor, ...rest }) => ({ + ...rest, + accessors: [xAccessor] as string[], + })), + // need the untouched ones for some checks later on + untouchedDataLayers: filteredDataLayers, + accessors: filteredDataLayers.map(({ xAccessor }) => xAccessor) as string[], }; - case 'yLeft': + } + case 'yLeft': { const { left } = groupAxesByType(dataLayers, activeData); + const leftIds = new Set(left.map(({ layer }) => layer)); + const filteredDataLayers = dataLayers.filter(({ layerId }) => leftIds.has(layerId)); return { - dataLayer: dataLayers.find(({ layerId }) => layerId === left[0]?.layer), - accessor: left[0]?.accessor, + dataLayers: filteredDataLayers, + untouchedDataLayers: filteredDataLayers, + accessors: left.map(({ accessor }) => accessor), }; - case 'yRight': + } + case 'yRight': { const { right } = groupAxesByType(dataLayers, activeData); + const rightIds = new Set(right.map(({ layer }) => layer)); + const filteredDataLayers = dataLayers.filter(({ layerId }) => rightIds.has(layerId)); return { - dataLayer: dataLayers.find(({ layerId }) => layerId === right[0]?.layer), - accessor: right[0]?.accessor, + dataLayers: filteredDataLayers, + untouchedDataLayers: filteredDataLayers, + accessors: right.map(({ accessor }) => accessor), }; + } + } +} + +export function computeOverallDataDomain( + dataLayers: Array>, + accessorIds: string[], + activeData: NonNullable +) { + const accessorMap = new Set(accessorIds); + let min: number | undefined; + let max: number | undefined; + const [stacked, unstacked] = partition(dataLayers, ({ seriesType }) => + isStackedChart(seriesType) + ); + for (const { layerId, accessors } of unstacked) { + const table = activeData[layerId]; + if (table) { + for (const accessor of accessors) { + if (accessorMap.has(accessor)) { + for (const row of table.rows) { + const value = row[accessor]; + if (typeof value === 'number') { + // when not stacked, do not keep the 0 + max = max != null ? Math.max(value, max) : value; + min = min != null ? Math.min(value, min) : value; + } + } + } + } + } + } + // stacked can span multiple layers, so compute an overall max/min by bucket + const stackedResults: Record = {}; + for (const { layerId, accessors, xAccessor } of stacked) { + const table = activeData[layerId]; + if (table) { + for (const accessor of accessors) { + if (accessorMap.has(accessor)) { + for (const row of table.rows) { + const value = row[accessor]; + // start with a shared bucket + let bucket = 'shared'; + // but if there's an xAccessor use it as new bucket system + if (xAccessor) { + bucket = row[xAccessor]; + } + if (typeof value === 'number') { + stackedResults[bucket] = stackedResults[bucket] ?? 0; + stackedResults[bucket] += value; + } + } + } + } + } + } + + for (const value of Object.values(stackedResults)) { + // for stacked extents keep 0 in view + max = Math.max(value, max || 0, 0); + min = Math.min(value, min || 0, 0); } + + return { min, max }; } function computeStaticValueForGroup( - dataLayer: XYLayerConfig | undefined, - accessorId: string | undefined, + dataLayers: Array>, + accessorIds: string[], activeData: NonNullable, - minZeroBased: boolean + minZeroOrNegativeBase: boolean = true ) { const defaultThresholdFactor = 3 / 4; - if (dataLayer && accessorId) { - if (isPercentageSeries(dataLayer?.seriesType)) { + if (dataLayers.length && accessorIds.length) { + if (dataLayers.some(({ seriesType }) => isPercentageSeries(seriesType))) { return defaultThresholdFactor; } - const tableId = Object.keys(activeData).find((key) => - activeData[key].columns.some(({ id }) => id === accessorId) - ); - if (tableId) { - const columnMax = activeData[tableId].rows.reduce( - (max, row) => Math.max(row[accessorId], max), - -Infinity - ); - const columnMin = activeData[tableId].rows.reduce( - (max, row) => Math.min(row[accessorId], max), - Infinity - ); + + const { min, max } = computeOverallDataDomain(dataLayers, accessorIds, activeData); + + if (min != null && max != null && isFinite(min) && isFinite(max)) { // Custom axis bounds can go below 0, so consider also lower values than 0 - const finalMinValue = minZeroBased ? Math.min(0, columnMin) : columnMin; - const interval = columnMax - finalMinValue; + const finalMinValue = minZeroOrNegativeBase ? Math.min(0, min) : min; + const interval = max - finalMinValue; return Number((finalMinValue + interval * defaultThresholdFactor).toFixed(2)); } } From 586682a0c480a37e04aa717aceb2dcfa39835495 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Thu, 14 Oct 2021 15:49:35 +0200 Subject: [PATCH 20/31] [Discover] Rename default column in the advanced settings (#114100) * [Discover] Rename default column in the advanced settings * Fix eslint * Rename default column to an empty string * Fix typo * Fix default column filtering * Update comment * Make an empty array a default columns * Improve functional test * Wording change Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/main/utils/get_state_defaults.ts | 11 +++++++-- .../application/helpers/state_helpers.ts | 7 +++++- src/plugins/discover/server/ui_settings.ts | 5 ++-- .../apps/discover/_discover_fields_api.ts | 24 ++++++++++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts b/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts index 11ebf0ecf9af4..f2f6e4a002aaf 100644 --- a/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts +++ b/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts @@ -6,9 +6,13 @@ * Side Public License, v 1. */ -import { cloneDeep } from 'lodash'; +import { cloneDeep, isEqual } from 'lodash'; import { IUiSettingsClient } from 'kibana/public'; -import { DEFAULT_COLUMNS_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; +import { + DEFAULT_COLUMNS_SETTING, + SEARCH_FIELDS_FROM_SOURCE, + SORT_DEFAULT_ORDER_SETTING, +} from '../../../../../common'; import { SavedSearch } from '../../../../saved_searches'; import { DataPublicPluginStart } from '../../../../../../data/public'; @@ -19,6 +23,9 @@ function getDefaultColumns(savedSearch: SavedSearch, config: IUiSettingsClient) if (savedSearch.columns && savedSearch.columns.length > 0) { return [...savedSearch.columns]; } + if (config.get(SEARCH_FIELDS_FROM_SOURCE) && isEqual(config.get(DEFAULT_COLUMNS_SETTING), [])) { + return ['_source']; + } return [...config.get(DEFAULT_COLUMNS_SETTING)]; } diff --git a/src/plugins/discover/public/application/helpers/state_helpers.ts b/src/plugins/discover/public/application/helpers/state_helpers.ts index fd17ec9516ab5..bb64f823d61a6 100644 --- a/src/plugins/discover/public/application/helpers/state_helpers.ts +++ b/src/plugins/discover/public/application/helpers/state_helpers.ts @@ -24,6 +24,7 @@ export function handleSourceColumnState( } const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); const defaultColumns = uiSettings.get(DEFAULT_COLUMNS_SETTING); + if (useNewFieldsApi) { // if fields API is used, filter out the source column let cleanedColumns = state.columns.filter((column) => column !== '_source'); @@ -39,9 +40,13 @@ export function handleSourceColumnState( } else if (state.columns.length === 0) { // if _source fetching is used and there are no column, switch back to default columns // this can happen if the fields API was previously used + const columns = defaultColumns; + if (columns.length === 0) { + columns.push('_source'); + } return { ...state, - columns: [...defaultColumns], + columns: [...columns], }; } diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index aa1b44da12bfc..82221fb8e8593 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -33,9 +33,10 @@ export const getUiSettings: () => Record = () => ({ name: i18n.translate('discover.advancedSettings.defaultColumnsTitle', { defaultMessage: 'Default columns', }), - value: ['_source'], + value: [], description: i18n.translate('discover.advancedSettings.defaultColumnsText', { - defaultMessage: 'Columns displayed by default in the Discovery tab', + defaultMessage: + 'Columns displayed by default in the Discover app. If empty, a summary of the document will be displayed.', }), category: ['discover'], schema: schema.arrayOf(schema.string()), diff --git a/test/functional/apps/discover/_discover_fields_api.ts b/test/functional/apps/discover/_discover_fields_api.ts index 42e2a94b36462..700c865031cd6 100644 --- a/test/functional/apps/discover/_discover_fields_api.ts +++ b/test/functional/apps/discover/_discover_fields_api.ts @@ -15,7 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker', 'settings']); const defaultSettings = { defaultIndex: 'logstash-*', 'discover:searchFieldsFromSource': false, @@ -67,5 +67,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickDocViewerTab(1); await PageObjects.discover.expectSourceViewerToExist(); }); + + it('switches to _source column when fields API is no longer used', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSettings(); + await PageObjects.settings.toggleAdvancedSettingCheckbox('discover:searchFieldsFromSource'); + + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + + expect(await PageObjects.discover.getDocHeader()).to.have.string('_source'); + }); + + it('switches to Document column when fields API is used', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSettings(); + await PageObjects.settings.toggleAdvancedSettingCheckbox('discover:searchFieldsFromSource'); + + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + + expect(await PageObjects.discover.getDocHeader()).to.have.string('Document'); + }); }); } From cdce98c8a363520e99f59f3f10c59a32586480a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Thu, 14 Oct 2021 15:57:27 +0200 Subject: [PATCH 21/31] [Stack monitoring] Fix clusters functional tests when react is enabled (#114982) * Fix test subjects for overview page * fix pathname matching --- .../public/application/pages/cluster/overview_page.tsx | 2 +- x-pack/plugins/monitoring/public/application/route_init.tsx | 2 +- x-pack/plugins/monitoring/public/directives/main/index.html | 2 +- x-pack/test/functional/services/monitoring/cluster_overview.js | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx index b78df27cd12c4..04074762c8d22 100644 --- a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx @@ -50,7 +50,7 @@ export const ClusterOverview: React.FC<{}> = () => { { id: 'clusterName', label: clusters[0].cluster_name, - testSubj: 'clusterName', + testSubj: 'overviewTabsclusterName', route: '/overview', }, ]; diff --git a/x-pack/plugins/monitoring/public/application/route_init.tsx b/x-pack/plugins/monitoring/public/application/route_init.tsx index c620229eb059a..52780aa280707 100644 --- a/x-pack/plugins/monitoring/public/application/route_init.tsx +++ b/x-pack/plugins/monitoring/public/application/route_init.tsx @@ -57,7 +57,7 @@ export const RouteInit: React.FC = ({ // check if we need to redirect because of attempt at unsupported multi-cluster monitoring const clusterSupported = cluster.isSupported || clusters.length === 1; - if (location.pathname !== 'home' && !clusterSupported) { + if (location.pathname !== '/home' && !clusterSupported) { return ; } } diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html index c989c71d8c1d4..fd14120e1db2f 100644 --- a/x-pack/plugins/monitoring/public/directives/main/index.html +++ b/x-pack/plugins/monitoring/public/directives/main/index.html @@ -299,7 +299,7 @@

{{pageTitle || monitoringMain.instance}}

= ({ hideSubtitle = false, }) => (
- + - + -

+

{title} {tooltip && ( <> @@ -81,7 +81,7 @@ const HeaderSectionComponent: React.FC = ({ )} -

+
{!hideSubtitle && ( diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx index e1546c5220e22..738103f02dcdf 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx @@ -30,33 +30,49 @@ const SHOW_TOP = (fieldName: string) => }); interface Props { + className?: string; /** When `Component` is used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality. * When `Component` is used with `EuiContextMenu`, we pass EuiContextMenuItem to render the right style. */ Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem; enablePopOver?: boolean; field: string; + flush?: 'left' | 'right' | 'both'; globalFilters?: Filter[]; + iconSide?: 'left' | 'right'; + iconType?: string; + isExpandable?: boolean; onClick: () => void; onFilterAdded?: () => void; ownFocus: boolean; + paddingSize?: 's' | 'm' | 'l' | 'none'; showTooltip?: boolean; showTopN: boolean; + showLegend?: boolean; timelineId?: string | null; + title?: string; value?: string[] | string | null; } export const ShowTopNButton: React.FC = React.memo( ({ + className, Component, enablePopOver, field, + flush, + iconSide, + iconType, + isExpandable, onClick, onFilterAdded, ownFocus, + paddingSize, + showLegend, showTooltip = true, showTopN, timelineId, + title, value, globalFilters, }) => { @@ -70,31 +86,36 @@ export const ShowTopNButton: React.FC = React.memo( ? SourcererScopeName.detections : SourcererScopeName.default; const { browserFields, indexPattern } = useSourcererScope(activeScope); - + const icon = iconType ?? 'visBarVertical'; + const side = iconSide ?? 'left'; + const buttonTitle = title ?? SHOW_TOP(field); const basicButton = useMemo( () => Component ? ( - {SHOW_TOP(field)} + {buttonTitle} ) : ( ), - [Component, field, onClick] + [Component, buttonTitle, className, flush, icon, onClick, side] ); const button = useMemo( @@ -107,7 +128,7 @@ export const ShowTopNButton: React.FC = React.memo( field, value, })} - content={SHOW_TOP(field)} + content={buttonTitle} shortcut={SHOW_TOP_N_KEYBOARD_SHORTCUT} showShortcut={ownFocus} /> @@ -118,7 +139,7 @@ export const ShowTopNButton: React.FC = React.memo( ) : ( basicButton ), - [basicButton, field, ownFocus, showTooltip, showTopN, value] + [basicButton, buttonTitle, field, ownFocus, showTooltip, showTopN, value] ); const topNPannel = useMemo( @@ -128,15 +149,37 @@ export const ShowTopNButton: React.FC = React.memo( field={field} indexPattern={indexPattern} onFilterAdded={onFilterAdded} + paddingSize={paddingSize} + showLegend={showLegend} timelineId={timelineId ?? undefined} toggleTopN={onClick} value={value} globalFilters={globalFilters} /> ), - [browserFields, field, indexPattern, onClick, onFilterAdded, timelineId, value, globalFilters] + [ + browserFields, + field, + indexPattern, + onFilterAdded, + paddingSize, + showLegend, + timelineId, + onClick, + value, + globalFilters, + ] ); + if (isExpandable) { + return ( + <> + {basicButton} + {showTopN && topNPannel} + + ); + } + return showTopN ? ( enablePopOver ? ( | PropsForAnchor> = + ({ children, ...props }) => {children}; + +export const LinkAnchor: React.FC = ({ children, ...props }) => ( + {children} +); + +export const Comma = styled('span')` + margin-right: 5px; + margin-left: 5px; + &::after { + content: ' ,'; + } +`; + +Comma.displayName = 'Comma'; + +const GenericLinkButtonComponent: React.FC<{ + children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; + dataTestSubj?: string; + href: string; + onClick?: (e: SyntheticEvent) => void; + title?: string; + iconType?: string; +}> = ({ children, Component, dataTestSubj, href, onClick, title, iconType = 'expand' }) => { + return Component ? ( + + {title ?? children} + + ) : ( + + {title ?? children} + + ); +}; + +export const GenericLinkButton = React.memo(GenericLinkButtonComponent); + +export const PortContainer = styled.div` + & svg { + position: relative; + top: -1px; + } +`; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 7db6b0204b649..c74791b8b3aa7 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -6,19 +6,15 @@ */ import { - EuiButton, - EuiButtonProps, - EuiLink, - EuiLinkProps, - EuiToolTip, + EuiButtonEmpty, + EuiButtonIcon, EuiFlexGroup, EuiFlexItem, - PropsForAnchor, - PropsForButton, + EuiLink, + EuiToolTip, } from '@elastic/eui'; import React, { useMemo, useCallback, SyntheticEvent } from 'react'; import { isNil } from 'lodash/fp'; -import styled from 'styled-components'; import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants'; import { @@ -43,22 +39,11 @@ import { isUrlInvalid } from '../../utils/validators'; import * as i18n from './translations'; import { SecurityPageName } from '../../../app/types'; import { getUebaDetailsUrl } from '../link_to/redirect_to_ueba'; +import { LinkButton, LinkAnchor, GenericLinkButton, PortContainer, Comma } from './helpers'; -export const DEFAULT_NUMBER_OF_LINK = 5; - -export const LinkButton: React.FC | PropsForAnchor> = - ({ children, ...props }) => {children}; +export { LinkButton, LinkAnchor } from './helpers'; -export const LinkAnchor: React.FC = ({ children, ...props }) => ( - {children} -); - -export const PortContainer = styled.div` - & svg { - position: relative; - top: -1px; - } -`; +export const DEFAULT_NUMBER_OF_LINK = 5; // Internal Links const UebaDetailsLinkComponent: React.FC<{ @@ -102,10 +87,13 @@ export const UebaDetailsLink = React.memo(UebaDetailsLinkComponent); const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; hostName: string; isButton?: boolean; onClick?: (e: SyntheticEvent) => void; -}> = ({ children, hostName, isButton, onClick }) => { + title?: string; +}> = ({ children, Component, hostName, isButton, onClick, title }) => { const { formatUrl, search } = useFormatUrl(SecurityPageName.hosts); const { navigateToApp } = useKibana().services.application; const goToHostDetails = useCallback( @@ -118,19 +106,25 @@ const HostDetailsLinkComponent: React.FC<{ }, [hostName, navigateToApp, search] ); - + const href = useMemo( + () => formatUrl(getHostDetailsUrl(encodeURIComponent(hostName))), + [formatUrl, hostName] + ); return isButton ? ( - - {children ? children : hostName} - + {children} + ) : ( {children ? children : hostName} @@ -176,11 +170,14 @@ ExternalLink.displayName = 'ExternalLink'; const NetworkDetailsLinkComponent: React.FC<{ children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; ip: string; flowTarget?: FlowTarget | FlowTargetSourceDest; isButton?: boolean; onClick?: (e: SyntheticEvent) => void | undefined; -}> = ({ children, ip, flowTarget = FlowTarget.source, isButton, onClick }) => { + title?: string; +}> = ({ Component, children, ip, flowTarget = FlowTarget.source, isButton, onClick, title }) => { const { formatUrl, search } = useFormatUrl(SecurityPageName.network); const { navigateToApp } = useKibana().services.application; const goToNetworkDetails = useCallback( @@ -193,19 +190,25 @@ const NetworkDetailsLinkComponent: React.FC<{ }, [flowTarget, ip, navigateToApp, search] ); + const href = useMemo( + () => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)))), + [formatUrl, ip] + ); return isButton ? ( - - {children ? children : ip} - + {children} + ) : ( {children ? children : ip} @@ -272,63 +275,84 @@ CreateCaseLink.displayName = 'CreateCaseLink'; // External Links export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>( - ({ children, link }) => ( - - {children ? children : link} - - ) + ({ children, link }) => { + const url = useMemo( + () => `https://www.google.com/search?q=${encodeURIComponent(link)}`, + [link] + ); + return {children ? children : link}; + } ); GoogleLink.displayName = 'GoogleLink'; export const PortOrServiceNameLink = React.memo<{ children?: React.ReactNode; + /** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */ + Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon; portOrServiceName: number | string; -}>(({ children, portOrServiceName }) => ( - - void | undefined; + title?: string; +}>(({ Component, title, children, portOrServiceName }) => { + const href = useMemo( + () => + `https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=${encodeURIComponent( String(portOrServiceName) - )}`} - target="_blank" + )}`, + [portOrServiceName] + ); + return Component ? ( + - {children ? children : portOrServiceName} - - -)); + {title ?? children ?? portOrServiceName} + + ) : ( + + + {children ? children : portOrServiceName} + + + ); +}); PortOrServiceNameLink.displayName = 'PortOrServiceNameLink'; export const Ja3FingerprintLink = React.memo<{ children?: React.ReactNode; ja3Fingerprint: string; -}>(({ children, ja3Fingerprint }) => ( - - {children ? children : ja3Fingerprint} - -)); +}>(({ children, ja3Fingerprint }) => { + const href = useMemo( + () => `https://sslbl.abuse.ch/ja3-fingerprints/${encodeURIComponent(ja3Fingerprint)}`, + [ja3Fingerprint] + ); + return ( + + {children ? children : ja3Fingerprint} + + ); +}); Ja3FingerprintLink.displayName = 'Ja3FingerprintLink'; export const CertificateFingerprintLink = React.memo<{ children?: React.ReactNode; certificateFingerprint: string; -}>(({ children, certificateFingerprint }) => ( - - {children ? children : certificateFingerprint} - -)); +}>(({ children, certificateFingerprint }) => { + const href = useMemo( + () => + `https://sslbl.abuse.ch/ssl-certificates/sha1/${encodeURIComponent(certificateFingerprint)}`, + [certificateFingerprint] + ); + return ( + + {children ? children : certificateFingerprint} + + ); +}); CertificateFingerprintLink.displayName = 'CertificateFingerprintLink'; @@ -354,16 +378,6 @@ const isReputationLink = ( (rowItem as ReputationLinkSetting).url_template !== undefined && (rowItem as ReputationLinkSetting).name !== undefined; -export const Comma = styled('span')` - margin-right: 5px; - margin-left: 5px; - &::after { - content: ' ,'; - } -`; - -Comma.displayName = 'Comma'; - const defaultNameMapping: Record = { [DefaultReputationLink['virustotal.com']]: i18n.VIEW_VIRUS_TOTAL, [DefaultReputationLink['talosIntelligence.com']]: i18n.VIEW_TALOS_INTELLIGENCE, @@ -463,11 +477,13 @@ ReputationLinkComponent.displayName = 'ReputationLinkComponent'; export const ReputationLink = React.memo(ReputationLinkComponent); export const WhoIsLink = React.memo<{ children?: React.ReactNode; domain: string }>( - ({ children, domain }) => ( - - {children ? children : domain} - - ) + ({ children, domain }) => { + const url = useMemo( + () => `https://www.iana.org/whois?q=${encodeURIComponent(domain)}`, + [domain] + ); + return {children ? children : domain}; + } ); WhoIsLink.displayName = 'WhoIsLink'; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index f25e8311ff8fe..7eac477741a5c 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -79,6 +79,7 @@ export const MatrixHistogramComponent: React.FC = legendPosition, mapping, onError, + paddingSize = 'm', panelHeight = DEFAULT_PANEL_HEIGHT, setAbsoluteRangeDatePickerTarget = 'global', setQuery, @@ -200,7 +201,11 @@ export const MatrixHistogramComponent: React.FC = return ( <> - + {loading && !isInitialLoading && (