From 747a6c644bfc5258720979cfb7cff2eb58894454 Mon Sep 17 00:00:00 2001 From: Baturalp Gurdin <9674241+suchcodemuchwow@users.noreply.github.com> Date: Wed, 8 Jun 2022 20:31:44 +0200 Subject: [PATCH 01/44] clean static worker kibana installs (#133930) --- .buildkite/scripts/steps/functional/performance_playwright.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index e4553fcf09ca1..9436552326d93 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -5,6 +5,8 @@ set -euo pipefail source .buildkite/scripts/common/util.sh .buildkite/scripts/bootstrap.sh +# These tests are running on static workers so we have to make sure we delete previous build of Kibana +rm -rf "$KIBANA_BUILD_LOCATION" .buildkite/scripts/download_build_artifacts.sh echo --- Run Performance Tests with Playwright config From 9984f653449c0ae4a0a9676175fa32e3a58c7052 Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Wed, 8 Jun 2022 20:38:26 +0200 Subject: [PATCH 02/44] Add names to some of user interactions on the rules management page (#133486) --- .../apm/__mocks__/use_start_transaction.ts | 10 ++++++ .../common/lib/apm/use_start_transaction.ts | 31 ++++++++++++++++ .../public/common/lib/apm/user_actions.ts | 35 +++++++++++++++++++ .../public/common/lib/kibana/kibana_react.ts | 2 ++ .../pre_packaged_rules/load_empty_prompt.tsx | 10 ++---- .../rule_actions_overflow/index.test.tsx | 1 + .../rules/rule_actions_overflow/index.tsx | 7 ++++ .../components/rules/rule_preview/index.tsx | 13 +++++-- .../rules/rule_switch/index.test.tsx | 1 + .../components/rules/rule_switch/index.tsx | 8 ++++- .../all/bulk_actions/use_bulk_actions.tsx | 15 ++++++-- .../detection_engine/rules/all/index.test.tsx | 3 -- .../detection_engine/rules/all/index.tsx | 3 -- .../rules/all/rules_table_actions.test.tsx | 7 ++-- .../rules/all/rules_table_actions.tsx | 15 +++++--- .../rules_table_filters.tsx | 19 +++++++--- .../rules/all/rules_table_toolbar.tsx | 26 ++++++++++++-- .../rules/all/rules_tables.tsx | 15 +++++--- .../rules/all/use_columns.tsx | 8 +++-- .../detection_engine/rules/edit/index.tsx | 6 ++++ .../pages/detection_engine/rules/index.tsx | 8 +++-- .../security_solution/public/plugin.tsx | 2 ++ .../plugins/security_solution/public/types.ts | 2 ++ 23 files changed, 205 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/lib/apm/__mocks__/use_start_transaction.ts create mode 100644 x-pack/plugins/security_solution/public/common/lib/apm/use_start_transaction.ts create mode 100644 x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/apm/__mocks__/use_start_transaction.ts b/x-pack/plugins/security_solution/public/common/lib/apm/__mocks__/use_start_transaction.ts new file mode 100644 index 0000000000000..4eb8c031f7f09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/apm/__mocks__/use_start_transaction.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export const useStartTransaction = jest.fn(() => ({ + startTransaction: jest.fn(), +})); diff --git a/x-pack/plugins/security_solution/public/common/lib/apm/use_start_transaction.ts b/x-pack/plugins/security_solution/public/common/lib/apm/use_start_transaction.ts new file mode 100644 index 0000000000000..2373c2542f6b4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/apm/use_start_transaction.ts @@ -0,0 +1,31 @@ +/* + * 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 { useCallback } from 'react'; +import { useKibana } from '../kibana'; + +const transactionOptions = { managed: true }; + +interface StartTransactionOptions { + name: string; + type?: string; +} + +export const useStartTransaction = () => { + const { + services: { apm }, + } = useKibana(); + + const startTransaction = useCallback( + ({ name, type = 'user-interaction' }: StartTransactionOptions) => { + return apm.startTransaction(name, type, transactionOptions); + }, + [apm] + ); + + return { startTransaction }; +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts b/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts new file mode 100644 index 0000000000000..51c3db6ffdfa4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/apm/user_actions.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +export const SECURITY_ACTIONS_PREFIX = 'securitySolution'; + +export const SINGLE_RULE_ACTIONS = { + ENABLE: `${SECURITY_ACTIONS_PREFIX} singleRuleActions enable`, + DISABLE: `${SECURITY_ACTIONS_PREFIX} singleRuleActions disable`, + DUPLICATE: `${SECURITY_ACTIONS_PREFIX} singleRuleActions duplicate`, + EXPORT: `${SECURITY_ACTIONS_PREFIX} singleRuleActions export`, + DELETE: `${SECURITY_ACTIONS_PREFIX} singleRuleActions delete`, + PREVIEW: `${SECURITY_ACTIONS_PREFIX} singleRuleActions preview`, + SAVE: `${SECURITY_ACTIONS_PREFIX} singleRuleActions save`, +}; + +export const BULK_RULE_ACTIONS = { + ENABLE: `${SECURITY_ACTIONS_PREFIX} bulkRuleActions enable`, + DISABLE: `${SECURITY_ACTIONS_PREFIX} bulkRuleActions disable`, + DUPLICATE: `${SECURITY_ACTIONS_PREFIX} bulkRuleActions duplicate`, + EXPORT: `${SECURITY_ACTIONS_PREFIX} bulkRuleActions export`, + DELETE: `${SECURITY_ACTIONS_PREFIX} bulkRuleActions delete`, + EDIT: `${SECURITY_ACTIONS_PREFIX} bulkRuleActions edit`, +}; + +export const RULES_TABLE_ACTIONS = { + REFRESH: `${SECURITY_ACTIONS_PREFIX} rulesTable refresh`, + FILTER: `${SECURITY_ACTIONS_PREFIX} rulesTable filter`, + LOAD_PREBUILT: `${SECURITY_ACTIONS_PREFIX} rulesTable loadPrebuilt`, + PREVIEW_ON: `${SECURITY_ACTIONS_PREFIX} rulesTable technicalPreview on`, + PREVIEW_OFF: `${SECURITY_ACTIONS_PREFIX} rulesTable technicalPreview off`, +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.ts index 32641541df6d9..61f6997c2b08d 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.ts @@ -13,6 +13,7 @@ import { useUiSetting$, withKibana, } from '@kbn/kibana-react-plugin/public'; +import type { ApmBase } from '@elastic/apm-rum'; import { StartServices } from '../../../types'; export type KibanaContext = KibanaReactContextValue; @@ -23,6 +24,7 @@ export interface WithKibanaProps { const useTypedKibana = () => useKibana(); export { + ApmBase, KibanaContextProvider, useTypedKibana as useKibana, useUiSetting, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 2d7551f1634c6..a633f5f76a610 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -6,7 +6,7 @@ */ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { memo, useCallback, useMemo } from 'react'; +import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; @@ -32,10 +32,6 @@ const PrePackagedRulesPromptComponent: React.FC = ( loading = false, userHasPermissions = false, }) => { - const handlePreBuiltCreation = useCallback(() => { - createPrePackagedRules(); - }, [createPrePackagedRules]); - const [{ isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite }] = useUserData(); @@ -51,11 +47,11 @@ const PrePackagedRulesPromptComponent: React.FC = ( () => getLoadPrebuiltRulesAndTemplatesButton({ isDisabled: !userHasPermissions || loading, - onClick: handlePreBuiltCreation, + onClick: createPrePackagedRules, fill: true, 'data-test-subj': 'load-prebuilt-rules', }), - [getLoadPrebuiltRulesAndTemplatesButton, handlePreBuiltCreation, userHasPermissions, loading] + [getLoadPrebuiltRulesAndTemplatesButton, createPrePackagedRules, userHasPermissions, loading] ); return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx index 6085fab4419ab..8e8c6f11975f5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx @@ -15,6 +15,7 @@ import { import { RuleActionsOverflow } from '.'; import { mockRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +jest.mock('../../../../common/lib/apm/use_start_transaction'); jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../../common/lib/kibana', () => { const actual = jest.requireActual('../../../../common/lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index d45159c61ce49..c59f6ec0bab12 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -20,6 +20,8 @@ import { BulkAction } from '../../../../../common/detection_engine/schemas/commo import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; +import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useKibana } from '../../../../common/lib/kibana'; import { getToolTipContent } from '../../../../common/utils/privileges'; import { Rule } from '../../../containers/detection_engine/rules'; @@ -58,6 +60,7 @@ const RuleActionsOverflowComponent = ({ const [isPopoverOpen, , closePopover, togglePopover] = useBoolState(); const { navigateToApp } = useKibana().services.application; const toasts = useAppToasts(); + const { startTransaction } = useStartTransaction(); const onRuleDeletedCallback = useCallback(() => { navigateToApp(APP_UI_ID, { @@ -76,6 +79,7 @@ const RuleActionsOverflowComponent = ({ disabled={!canDuplicateRuleWithActions || !userHasPermissions} data-test-subj="rules-details-duplicate-rule" onClick={async () => { + startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); closePopover(); const result = await executeRulesBulkAction({ action: BulkAction.duplicate, @@ -102,6 +106,7 @@ const RuleActionsOverflowComponent = ({ disabled={!userHasPermissions || rule.immutable} data-test-subj="rules-details-export-rule" onClick={async () => { + startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); closePopover(); await executeRulesBulkAction({ action: BulkAction.export, @@ -119,6 +124,7 @@ const RuleActionsOverflowComponent = ({ disabled={!userHasPermissions} data-test-subj="rules-details-delete-rule" onClick={async () => { + startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); closePopover(); await executeRulesBulkAction({ action: BulkAction.delete, @@ -138,6 +144,7 @@ const RuleActionsOverflowComponent = ({ navigateToApp, onRuleDeletedCallback, rule, + startTransaction, toasts, userHasPermissions, ] diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx index 8fa89669bddcb..20ed47fffda2f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { Unit } from '@kbn/datemath'; import { ThreatMapping, Type } from '@kbn/securitysolution-io-ts-alerting-types'; import styled from 'styled-components'; @@ -29,6 +29,8 @@ import { LoadingHistogram } from './loading_histogram'; import { FieldValueThreshold } from '../threshold_input'; import { isJobStarted } from '../../../../../common/machine_learning/helpers'; import { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; +import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; const HelpTextComponent = ( @@ -123,6 +125,13 @@ const RulePreviewComponent: React.FC = ({ setTimeFrame(defaultTimeRange); }, [ruleType]); + const { startTransaction } = useStartTransaction(); + + const handlePreviewClick = useCallback(() => { + startTransaction({ name: SINGLE_RULE_ACTIONS.PREVIEW }); + createPreview(); + }, [createPreview, startTransaction]); + return ( <> = ({ fill isLoading={isPreviewRequestInProgress} isDisabled={isDisabled || !areRelaventMlJobsRunning} - onClick={createPreview} + onClick={handlePreviewClick} data-test-subj="queryPreviewButton" > {i18n.QUERY_PREVIEW_BUTTON} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx index 68e7d9a973b85..6580d8955ae2f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx @@ -21,6 +21,7 @@ import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../containers/detection_engine/rules'); jest.mock('../../../pages/detection_engine/rules/all/rules_table/rules_table_context'); +jest.mock('../../../../common/lib/apm/use_start_transaction'); const useAppToastsValueMock = useAppToastsMock.create(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index a5f80de7acbdc..f26b2f356e3db 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -17,6 +17,8 @@ import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { BulkAction } from '../../../../../common/detection_engine/schemas/common'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useUpdateRulesCache } from '../../../containers/detection_engine/rules/use_find_rules_query'; import { executeRulesBulkAction } from '../../../pages/detection_engine/rules/all/actions'; import { useRulesTableContextOptional } from '../../../pages/detection_engine/rules/all/rules_table/rules_table_context'; @@ -52,10 +54,14 @@ export const RuleSwitchComponent = ({ const rulesTableContext = useRulesTableContextOptional(); const updateRulesCache = useUpdateRulesCache(); const toasts = useAppToasts(); + const { startTransaction } = useStartTransaction(); const onRuleStateChange = useCallback( async (event: EuiSwitchEvent) => { setMyIsLoading(true); + startTransaction({ + name: enabled ? SINGLE_RULE_ACTIONS.DISABLE : SINGLE_RULE_ACTIONS.ENABLE, + }); const bulkActionResponse = await executeRulesBulkAction({ setLoadingRules: rulesTableContext?.actions.setLoadingRules, toasts, @@ -71,7 +77,7 @@ export const RuleSwitchComponent = ({ } setMyIsLoading(false); }, - [id, onChange, rulesTableContext, toasts, updateRulesCache] + [enabled, id, onChange, rulesTableContext, startTransaction, toasts, updateRulesCache] ); const showLoader = useMemo((): boolean => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx index 44f040bb1ab1e..cf67af8131942 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx @@ -39,6 +39,8 @@ import { convertRulesFilterToKQL } from '../../../../../containers/detection_eng import type { FilterOptions } from '../../../../../containers/detection_engine/rules/types'; import { useInvalidateRules } from '../../../../../containers/detection_engine/rules/use_find_rules_query'; +import { BULK_RULE_ACTIONS } from '../../../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../../../common/lib/apm/use_start_transaction'; interface UseBulkActionsArgs { filterOptions: FilterOptions; @@ -65,6 +67,7 @@ export const useBulkActions = ({ const toasts = useAppToasts(); const getIsMounted = useIsMounted(); const filterQuery = convertRulesFilterToKQL(filterOptions); + const { startTransaction } = useStartTransaction(); // refetch tags if edit action is related to tags: add_tags/delete_tags/set_tags const resolveTagsRefetch = useCallback( @@ -99,6 +102,7 @@ export const useBulkActions = ({ selectedRules.some((rule) => !canEditRuleWithActions(rule, hasActionsPrivileges)); const handleEnableAction = async () => { + startTransaction({ name: BULK_RULE_ACTIONS.ENABLE }); closePopover(); const disabledRules = selectedRules.filter(({ enabled }) => !enabled); const disabledRulesNoML = disabledRules.filter(({ type }) => !isMlRule(type)); @@ -123,6 +127,7 @@ export const useBulkActions = ({ }; const handleDisableActions = async () => { + startTransaction({ name: BULK_RULE_ACTIONS.DISABLE }); closePopover(); const enabledIds = selectedRules.filter(({ enabled }) => enabled).map(({ id }) => id); await executeRulesBulkAction({ @@ -136,6 +141,7 @@ export const useBulkActions = ({ }; const handleDuplicateAction = async () => { + startTransaction({ name: BULK_RULE_ACTIONS.DUPLICATE }); closePopover(); await executeRulesBulkAction({ visibleRuleIds: selectedRuleIds, @@ -156,6 +162,7 @@ export const useBulkActions = ({ } } + startTransaction({ name: BULK_RULE_ACTIONS.DELETE }); await executeRulesBulkAction({ visibleRuleIds: selectedRuleIds, action: BulkAction.delete, @@ -169,6 +176,7 @@ export const useBulkActions = ({ const handleExportAction = async () => { closePopover(); + startTransaction({ name: BULK_RULE_ACTIONS.EXPORT }); await executeRulesBulkAction({ visibleRuleIds: selectedRuleIds, action: BulkAction.export, @@ -202,6 +210,8 @@ export const useBulkActions = ({ return; } + startTransaction({ name: BULK_RULE_ACTIONS.EDIT }); + const hideWarningToast = () => { if (longTimeWarningToast) { toasts.api.remove(longTimeWarningToast); @@ -415,18 +425,19 @@ export const useBulkActions = ({ hasActionsPrivileges, isAllSelected, loadingRuleIds, + startTransaction, hasMlPermissions, - invalidateRules, setLoadingRules, toasts, filterQuery, + invalidateRules, confirmDeletion, setIsRefreshOn, confirmBulkEdit, completeBulkEditForm, queryClient, - getIsMounted, filterOptions, + getIsMounted, resolveTagsRefetch, ] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index c5146f245cc8e..826cdca510a62 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -41,7 +41,6 @@ describe('AllRules', () => { { { ( ({ createPrePackagedRules, hasPermissions, - loading, loadingCreatePrePackagedRules, rulesCustomInstalled, rulesInstalled, @@ -50,7 +48,6 @@ export const AllRules = React.memo( { const toasts = useAppToastsMock.create(); const invalidateRules = jest.fn(); const setLoadingRules = jest.fn(); + const startTransaction = jest.fn(); beforeEach(() => { jest.clearAllMocks(); @@ -39,7 +40,8 @@ describe('getRulesTableActions', () => { navigateToApp, invalidateRules, true, - setLoadingRules + setLoadingRules, + startTransaction )[1]; const duplicateRulesActionHandler = duplicateRulesActionObject.onClick; expect(duplicateRulesActionHandler).toBeDefined(); @@ -59,7 +61,8 @@ describe('getRulesTableActions', () => { navigateToApp, invalidateRules, true, - setLoadingRules + setLoadingRules, + startTransaction )[3]; const deleteRuleActionHandler = deleteRulesActionObject.onClick; expect(deleteRuleActionHandler).toBeDefined(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx index 91aa36b62f9f7..9bbbcb7d55cab 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx @@ -20,6 +20,8 @@ import { Rule } from '../../../../containers/detection_engine/rules'; import * as i18n from '../translations'; import { executeRulesBulkAction, goToRuleEditPage } from './actions'; import { RulesTableActions } from './rules_table/rules_table_context'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; +import { SINGLE_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; type NavigateToApp = (appId: string, options?: NavigateToAppOptions | undefined) => Promise; @@ -30,7 +32,8 @@ export const getRulesTableActions = ( navigateToApp: NavigateToApp, invalidateRules: () => void, actionsPrivileges: boolean, - setLoadingRules: RulesTableActions['setLoadingRules'] + setLoadingRules: RulesTableActions['setLoadingRules'], + startTransaction: ReturnType['startTransaction'] ): Array> => [ { type: 'icon', @@ -61,6 +64,7 @@ export const getRulesTableActions = ( ), enabled: (rule: Rule) => canEditRuleWithActions(rule, actionsPrivileges), onClick: async (rule: Rule) => { + startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); const result = await executeRulesBulkAction({ action: BulkAction.duplicate, setLoadingRules, @@ -81,14 +85,16 @@ export const getRulesTableActions = ( description: i18n.EXPORT_RULE, icon: 'exportAction', name: i18n.EXPORT_RULE, - onClick: (rule: Rule) => - executeRulesBulkAction({ + onClick: async (rule: Rule) => { + startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); + await executeRulesBulkAction({ action: BulkAction.export, setLoadingRules, visibleRuleIds: [rule.id], toasts, search: { ids: [rule.id] }, - }), + }); + }, enabled: (rule: Rule) => !rule.immutable, }, { @@ -98,6 +104,7 @@ export const getRulesTableActions = ( icon: 'trash', name: i18n.DELETE_RULE, onClick: async (rule: Rule) => { + startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); await executeRulesBulkAction({ action: BulkAction.delete, setLoadingRules, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx index 18b2312ce5465..561744dc3dd50 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -15,6 +15,8 @@ import { import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; +import { RULES_TABLE_ACTIONS } from '../../../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../../../common/lib/apm/use_start_transaction'; import * as i18n from '../../translations'; import { useRulesTableContext } from '../rules_table/rules_table_context'; import { TagsFilterPopover } from './tags_filter_popover'; @@ -47,6 +49,7 @@ const RulesTableFiltersComponent = ({ rulesInstalled, allTags, }: RulesTableFiltersProps) => { + const { startTransaction } = useStartTransaction(); const { state: { filterOptions }, actions: { setFilterOptions }, @@ -55,25 +58,31 @@ const RulesTableFiltersComponent = ({ const { showCustomRules, showElasticRules, tags: selectedTags } = filterOptions; const handleOnSearch = useCallback( - (filterString) => setFilterOptions({ filter: filterString.trim() }), - [setFilterOptions] + (filterString) => { + startTransaction({ name: RULES_TABLE_ACTIONS.FILTER }); + setFilterOptions({ filter: filterString.trim() }); + }, + [setFilterOptions, startTransaction] ); const handleElasticRulesClick = useCallback(() => { + startTransaction({ name: RULES_TABLE_ACTIONS.FILTER }); setFilterOptions({ showElasticRules: !showElasticRules, showCustomRules: false }); - }, [setFilterOptions, showElasticRules]); + }, [setFilterOptions, showElasticRules, startTransaction]); const handleCustomRulesClick = useCallback(() => { + startTransaction({ name: RULES_TABLE_ACTIONS.FILTER }); setFilterOptions({ showCustomRules: !showCustomRules, showElasticRules: false }); - }, [setFilterOptions, showCustomRules]); + }, [setFilterOptions, showCustomRules, startTransaction]); const handleSelectedTags = useCallback( (newTags: string[]) => { if (!isEqual(newTags, selectedTags)) { + startTransaction({ name: RULES_TABLE_ACTIONS.FILTER }); setFilterOptions({ tags: newTags }); } }, - [selectedTags, setFilterOptions] + [selectedTags, setFilterOptions, startTransaction] ); return ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx index 261e14fd1411b..37ee0e5a27fb9 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import { EuiSwitch, EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; -import React from 'react'; +import { EuiSwitch, EuiSwitchEvent, EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; +import React, { useCallback } from 'react'; import styled from 'styled-components'; import { useRulesTableContext } from './rules_table/rules_table_context'; import * as i18n from '../translations'; +import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; const ToolbarLayout = styled.div` display: grid; @@ -48,6 +50,19 @@ export const RulesTableToolbar = React.memo( state: { isInMemorySorting }, actions: { setIsInMemorySorting }, } = useRulesTableContext(); + const { startTransaction } = useStartTransaction(); + + const handleInMemorySwitch = useCallback( + (e: EuiSwitchEvent) => { + startTransaction({ + name: isInMemorySorting + ? RULES_TABLE_ACTIONS.PREVIEW_OFF + : RULES_TABLE_ACTIONS.PREVIEW_ON, + }); + setIsInMemorySorting(e.target.checked); + }, + [isInMemorySorting, setIsInMemorySorting, startTransaction] + ); return ( @@ -66,9 +81,14 @@ export const RulesTableToolbar = React.memo( setIsInMemorySorting(e.target.checked)} + onChange={handleInMemorySwitch} /> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 3962fa217ccfb..0693da95ace82 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -43,13 +43,14 @@ import { useBulkEditFormFlyout } from './bulk_actions/use_bulk_edit_form_flyout' import { BulkEditConfirmation } from './bulk_actions/bulk_edit_confirmation'; import { BulkEditFlyout } from './bulk_actions/bulk_edit_flyout'; import { useBulkActions } from './bulk_actions/use_bulk_actions'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; +import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; const INITIAL_SORT_FIELD = 'enabled'; interface RulesTableProps { createPrePackagedRules: CreatePreBuiltRules | null; hasPermissions: boolean; - loading: boolean; loadingCreatePrePackagedRules: boolean; rulesCustomInstalled: number | null; rulesInstalled: number | null; @@ -74,7 +75,6 @@ export const RulesTables = React.memo( ({ createPrePackagedRules, hasPermissions, - loading, loadingCreatePrePackagedRules, rulesCustomInstalled, rulesInstalled, @@ -82,6 +82,7 @@ export const RulesTables = React.memo( rulesNotUpdated, selectedTab, }) => { + const { startTransaction } = useStartTransaction(); const tableRef = useRef(null); const rulesTableContext = useRulesTableContext(); @@ -198,10 +199,16 @@ export const RulesTables = React.memo( const handleCreatePrePackagedRules = useCallback(async () => { if (createPrePackagedRules != null) { + startTransaction({ name: RULES_TABLE_ACTIONS.LOAD_PREBUILT }); await createPrePackagedRules(); await reFetchRules(); } - }, [createPrePackagedRules, reFetchRules]); + }, [createPrePackagedRules, reFetchRules, startTransaction]); + + const handleRefreshRules = useCallback(() => { + startTransaction({ name: RULES_TABLE_ACTIONS.REFRESH }); + reFetchRules(); + }, [reFetchRules, startTransaction]); const isSelectAllCalled = useRef(false); @@ -349,7 +356,7 @@ export const RulesTables = React.memo( paginationTotal={pagination.total ?? 0} numberSelectedItems={selectedItemsCount} onGetBulkItemsPopoverContent={getBulkItemsPopoverContent} - onRefresh={reFetchRules} + onRefresh={handleRefreshRules} isAutoRefreshOn={isRefreshOn} onRefreshSwitch={handleAutoRefreshSwitch} isAllSelected={isAllSelected} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx index c28776d97d683..602ff0eee932e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx @@ -46,6 +46,7 @@ import { RuleExecutionSummary, } from '../../../../../../common/detection_engine/schemas/common'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -73,7 +74,6 @@ const useEnabledColumn = ({ hasPermissions }: ColumnsProps): TableColumn => { content={getToolTipContent(rule, hasMlPermissions, hasActionsPrivileges)} > => { const hasActionsPrivileges = useHasActionsPrivileges(); const toasts = useAppToasts(); const { reFetchRules, setLoadingRules } = useRulesTableContext().actions; + const { startTransaction } = useStartTransaction(); return useMemo( () => ({ @@ -186,11 +187,12 @@ const useActionsColumn = (): EuiTableActionsColumnType => { navigateToApp, reFetchRules, hasActionsPrivileges, - setLoadingRules + setLoadingRules, + startTransaction ), width: '40px', }), - [hasActionsPrivileges, navigateToApp, reFetchRules, setLoadingRules, toasts] + [hasActionsPrivileges, navigateToApp, reFetchRules, setLoadingRules, startTransaction, toasts] ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index d73049bd01cbc..6734636853a08 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -57,6 +57,8 @@ import { ruleStepsOrder } from '../utils'; import { useKibana } from '../../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../../common/constants'; import { HeaderPage } from '../../../../../common/components/header_page'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; +import { SINGLE_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; const formHookNoop = async (): Promise => undefined; @@ -227,6 +229,8 @@ const EditRulePageComponent: FC = () => { ] ); + const { startTransaction } = useStartTransaction(); + const onSubmit = useCallback(async () => { const activeStepData = await formHooks.current[activeStep](); if (activeStepData?.data != null) { @@ -243,6 +247,7 @@ const EditRulePageComponent: FC = () => { stepIsValid(schedule) && stepIsValid(actions) ) { + startTransaction({ name: SINGLE_RULE_ACTIONS.SAVE }); setRule({ ...formatRule( define.data, @@ -265,6 +270,7 @@ const EditRulePageComponent: FC = () => { scheduleStep, setRule, setStepData, + startTransaction, ]); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index d4691fe90e7af..9afc9086caa65 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -37,11 +37,14 @@ import { HeaderPage } from '../../../../common/components/header_page'; import { RulesTableContextProvider } from './all/rules_table/rules_table_context'; import { useInvalidateRules } from '../../../containers/detection_engine/rules/use_find_rules_query'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; +import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; const RulesPageComponent: React.FC = () => { const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState(); const { navigateToApp } = useKibana().services.application; + const { startTransaction } = useStartTransaction(); const invalidateRules = useInvalidateRules(); const [ @@ -62,7 +65,6 @@ const RulesPageComponent: React.FC = () => { const loading = userInfoLoading || listsConfigLoading; const { createPrePackagedRules, - loading: prePackagedRuleLoading, loadingCreatePrePackagedRules, refetchPrePackagedRulesStatus, rulesCustomInstalled, @@ -95,10 +97,11 @@ const RulesPageComponent: React.FC = () => { const handleCreatePrePackagedRules = useCallback(async () => { if (createPrePackagedRules != null) { + startTransaction({ name: RULES_TABLE_ACTIONS.LOAD_PREBUILT }); await createPrePackagedRules(); invalidateRules(); } - }, [createPrePackagedRules, invalidateRules]); + }, [createPrePackagedRules, invalidateRules, startTransaction]); const handleRefetchPrePackagedRulesStatus = useCallback(() => { if (refetchPrePackagedRulesStatus != null) { @@ -224,7 +227,6 @@ const RulesPageComponent: React.FC = () => { = (async () => { const [coreStart, startPlugins] = await core.getStartServices(); + const { apm } = await import('@elastic/apm-rum'); const services: StartServices = { ...coreStart, ...startPlugins, + apm, storage: this.storage, security: plugins.security, }; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 4e274a59349a0..cbd7244ad563e 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -33,6 +33,7 @@ import type { LicensingPluginStart, LicensingPluginSetup } from '@kbn/licensing- import type { DashboardStart } from '@kbn/dashboard-plugin/public'; import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { ApmBase } from '@elastic/apm-rum'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -84,6 +85,7 @@ export type StartServices = CoreStart & StartPlugins & { security: SecurityPluginSetup; storage: Storage; + apm: ApmBase; }; export interface PluginSetup { From 1fd9a76226f1c5fc43457f44a137ed55a804f677 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 8 Jun 2022 19:43:33 +0100 Subject: [PATCH 03/44] [Security Solution][Detection rules] fixes ungraceful failure when rule details page opened on non-existent rule #133867 ## Summary fixes https://github.com/elastic/kibana/issues/124355 In this PR, when results of fallback alerts search are empty, we return `undefined` for rule, instead of `{}` in helper method `transformRuleFromAlertHit`. Returning `{}` caused break in typings, allowed return types for that method are only `Rule` | `undefined` ### Before Screenshot 2022-06-08 at 11 49 31 ### After Screenshot 2022-06-08 at 11 48 59 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../detection_engine/alerts/mock.ts | 18 ++++++++ .../rules/use_rule_with_fallback.test.tsx | 42 ++++++++++++++----- .../rules/use_rule_with_fallback.tsx | 12 ++++-- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts index f35c4158fa236..451674fabe143 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts @@ -925,6 +925,24 @@ export const alertsMock: AlertSearchResponse = { }, }; +export const alertMockEmptyResults: AlertSearchResponse = { + took: 3, + timeout: false, + _shards: { + total: 1, + successful: 1, + skipped: 1, + failed: 0, + }, + hits: { + total: { + value: 0, + relation: 'gte', + }, + hits: [], + }, +}; + export const alertsMock8x: AlertSearchResponse = { took: 3, timeout: false, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx index 1816fd4c5a7af..c181aeccf7bf2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { SecurityAppError } from '@kbn/securitysolution-t-grid'; -import { alertsMock8x } from '../alerts/mock'; +import { alertsMock8x, alertMockEmptyResults } from '../alerts/mock'; import { AlertSearchResponse } from '../alerts/types'; import { useRuleWithFallback } from './use_rule_with_fallback'; import * as api from './api'; @@ -19,6 +19,14 @@ jest.mock('./api'); jest.mock('../alerts/api'); jest.mock('../../../../common/hooks/use_app_toasts'); +const mockNotFoundErrorForRule = () => { + (api.fetchRuleById as jest.Mock).mockImplementation(async () => { + const err = new Error('Not found') as SecurityAppError; + err.body = { status_code: 404, message: 'Rule Not found' }; + throw err; + }); +}; + describe('useRuleWithFallback', () => { (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); @@ -102,11 +110,7 @@ describe('useRuleWithFallback', () => { }); it("should fallback to fetching rule data from a 7.x signal if the rule doesn't exist", async () => { - (api.fetchRuleById as jest.Mock).mockImplementation(async () => { - const err = new Error('Not found') as SecurityAppError; - err.body = { status_code: 404, message: 'Rule Not found' }; - throw err; - }); + mockNotFoundErrorForRule(); await act(async () => { const { result, waitForNextUpdate } = renderHook((id) => useRuleWithFallback(id), { initialProps: 'testRuleId', @@ -217,11 +221,7 @@ describe('useRuleWithFallback', () => { return alertsMock8x as AlertSearchResponse; }); - (api.fetchRuleById as jest.Mock).mockImplementation(async () => { - const err = new Error('Not found') as SecurityAppError; - err.body = { status_code: 404, message: 'Rule Not found' }; - throw err; - }); + mockNotFoundErrorForRule(); await act(async () => { const { result, waitForNextUpdate } = renderHook((id) => useRuleWithFallback(id), { @@ -354,4 +354,24 @@ describe('useRuleWithFallback', () => { // Reset back to default mock coming from ../alerts/__mocks__/api.ts spy.mockRestore(); }); + + it('should return rule as null if fallback fetching from 8.0 alert returns empty results', async () => { + // Override default mock coming from ../alerts/__mocks__/api.ts + const spy = jest.spyOn(alertsAPI, 'fetchQueryAlerts').mockImplementation(async () => { + return alertMockEmptyResults; + }); + + mockNotFoundErrorForRule(); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook((id) => useRuleWithFallback(id), { + initialProps: 'testRuleId', + }); + await waitForNextUpdate(); + + expect(result.current.rule).toBeNull(); + }); + // Reset back to default mock coming from ../alerts/__mocks__/api.ts + spy.mockRestore(); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx index cda7b7aab0af2..3b2f7d8381b59 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx @@ -130,15 +130,19 @@ export const useRuleWithFallback = (ruleId: string): UseRuleWithFallback => { * Transforms an alertHit into a Rule * @param data raw response containing single alert */ -export const transformRuleFromAlertHit = (data: AlertSearchResponse): Rule | undefined => { - const hit = data?.hits.hits[0] as AlertHit | undefined; +const transformRuleFromAlertHit = (data: AlertSearchResponse): Rule | undefined => { + // if results empty, return rule as undefined + if (data.hits.hits.length === 0) { + return undefined; + } + const hit = data.hits.hits[0]; // If pre 8.x alert, pull directly from alertHit - const rule = hit?._source.signal?.rule ?? hit?._source.kibana?.alert?.rule; + const rule = hit._source.signal?.rule ?? hit._source.kibana?.alert?.rule; // If rule undefined, response likely flattened if (rule == null) { - const expandedRuleWithParams = expandDottedObject(hit?._source ?? {}) as RACRule; + const expandedRuleWithParams = expandDottedObject(hit._source ?? {}) as RACRule; const expandedRule = { ...expandedRuleWithParams?.kibana?.alert?.rule, ...expandedRuleWithParams?.kibana?.alert?.rule?.parameters, From e34de5b98955f8fe803083d620eadfeaf84cf0f4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 8 Jun 2022 13:46:40 -0500 Subject: [PATCH 04/44] [ironbank] null Kibana shasum (#133898) The Ironbank Docker context contains a list of checksums for installed assets. The Kibana checksum listed currently contains a static, outdated checksum from a previous release. This is usually replaced at publish time via automation, but in cases where we have to do a manual release it can be easy to overlook and cause confusion. This nulls out the checksum to make it clear it hasn't been updated. --- .../docker_generator/templates/ironbank/hardening_manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml index 9ec34316262c5..4672a33013035 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml @@ -34,7 +34,7 @@ resources: url: /kibana-{{version}}-linux-x86_64.tar.gz validation: type: sha512 - value: aa68f850cc09cf5dcb7c0b48bb8df788ca58eaad38d96141b8e59917fd38b42c728c0968f7cb2c8132c5aaeb595525cdde0859554346c496f53c569e03abe412 + value: null - filename: tini url: https://github.com/krallin/tini/releases/download/v0.19.0/tini-amd64 validation: From f7ef262864cc5670b9c0bc3b3c147b6003aae7fd Mon Sep 17 00:00:00 2001 From: Kristof C Date: Wed, 8 Jun 2022 14:02:39 -0500 Subject: [PATCH 05/44] 133809-update-rule-file-picker-to-only-allow-ndjson-filetype (#133811) * 133809-update-rule-file-picker-to-only-allow-ndjson-filetype * update snapshot for ImportDataModal Co-authored-by: Kristof-Pierre Cummings --- .../import_data_modal/__snapshots__/index.test.tsx.snap | 1 + .../public/common/components/import_data_modal/index.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap index ce5deaacf9f63..1e93394660808 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap @@ -23,6 +23,7 @@ exports[`ImportDataModal renders correctly against snapshot 1`] = ` size="s" /> { From 4cb3f601fd266838b60d15016ec5a9e8ee9f2ea9 Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Wed, 8 Jun 2022 11:07:52 -0800 Subject: [PATCH 06/44] [Detection Rules] Add 8.3 rules (#133845) --- ...tion_added_to_google_workspace_domain.json | 2 +- ...ion_email_powershell_exchange_mailbox.json | 4 +- ...llection_microsoft_365_new_inbox_rule.json | 4 +- .../collection_posh_audio_capture.json | 4 +- .../collection_posh_keylogger.json | 4 +- .../collection_posh_screen_grabber.json | 4 +- .../collection_winrar_encryption.json | 4 +- ...d_control_certutil_network_connection.json | 4 +- ...ommand_and_control_common_webservices.json | 6 +- ...ction_attempt_by_non_ssh_root_session.json | 74 + ...nd_and_control_dns_tunneling_nslookup.json | 4 +- ...ontrol_port_forwarding_added_registry.json | 4 +- ...te_desktop_protocol_from_the_internet.json | 2 +- .../command_and_control_rdp_tunnel_plink.json | 4 +- ...ol_remote_file_copy_desktopimgdownldr.json | 4 +- ...and_control_remote_file_copy_mpcmdrun.json | 4 +- ...d_control_remote_file_copy_powershell.json | 4 +- ..._and_control_remote_file_copy_scripts.json | 4 +- ...control_sunburst_c2_activity_detected.json | 4 +- ...d_control_teamviewer_remote_file_copy.json | 4 +- ...mand_and_control_telnet_port_activity.json | 2 +- ...tial_access_dcsync_replication_rights.json | 4 +- ...ntial_access_disable_kerberos_preauth.json | 6 +- ...cess_domain_backup_dpapi_private_keys.json | 4 +- ...credential_access_dump_registry_hives.json | 4 +- ..._access_kerberoasting_unusual_process.json | 4 +- ...ial_access_lsass_memdump_file_created.json | 2 +- ...al_access_lsass_memdump_handle_access.json | 4 +- ..._365_brute_force_user_account_attempt.json | 4 +- ...65_potential_password_spraying_attack.json | 4 +- ...l_access_mimikatz_memssp_default_logs.json | 4 +- ...ial_access_mimikatz_powershell_module.json | 57 + ..._access_mod_wdigest_security_provider.json | 4 +- ...l_access_moving_registry_hive_via_smb.json | 6 +- .../credential_access_posh_minidump.json | 4 +- ...credential_access_posh_request_ticket.json | 4 +- ...cess_relay_ntlm_auth_via_http_spoolss.json | 50 + ...dential_access_remote_sam_secretsdump.json | 6 +- ...redential_access_saved_creds_vaultcmd.json | 4 +- ...edelegationprivilege_assigned_to_user.json | 4 +- ...dential_access_spn_attribute_modified.json | 6 +- ...cious_winreg_access_via_sebackup_priv.json | 4 +- ..._symbolic_link_to_shadow_copy_created.json | 4 +- ...den_file_attribute_with_via_attribexe.json | 2 +- .../defense_evasion_amsienable_key_mod.json | 4 +- ...evasion_attempt_to_disable_gatekeeper.json | 4 +- ...sion_clearing_windows_console_history.json | 6 +- ...e_evasion_clearing_windows_event_logs.json | 4 +- ...vasion_clearing_windows_security_logs.json | 3 +- ...efense_evasion_code_injection_conhost.json | 4 +- ...vasion_defender_disabled_via_registry.json | 4 +- ...ion_defender_exclusion_via_powershell.json | 4 +- ...deletion_of_bash_command_line_history.json | 5 +- ...asion_disable_posh_scriptblocklogging.json | 4 +- ...ble_windows_firewall_rules_with_netsh.json | 4 +- ...disabling_windows_defender_powershell.json | 4 +- ...efense_evasion_disabling_windows_logs.json | 19 +- ...sion_elastic_agent_service_terminated.json | 54 + ...evasion_enable_inbound_rdp_with_netsh.json | 4 +- ...n_enable_network_discovery_with_netsh.json | 4 +- ...ense_evasion_execution_lolbas_wuauclt.json | 2 +- ..._evasion_microsoft_defender_tampering.json | 4 +- ...e_evasion_ms_office_suspicious_regmod.json | 4 +- .../defense_evasion_posh_assembly_load.json | 3 +- .../defense_evasion_posh_compressed.json | 3 +- ...efense_evasion_posh_process_injection.json | 4 +- ..._powershell_windows_firewall_disabled.json | 4 +- ...tion_privacy_pref_sshd_fulldiskaccess.json | 4 +- ...ense_evasion_proxy_execution_via_msdt.json | 50 + ..._evasion_suspicious_certutil_commands.json | 2 +- ...on_whitespace_padding_in_command_line.json | 4 +- ...evasion_workfolders_control_execution.json | 6 +- .../discovery_adfind_command_activity.json | 4 +- .../discovery_admin_recon.json | 4 +- .../discovery_command_system_account.json | 47 + .../discovery_file_dir_discovery.json | 3 +- ...on => discovery_linux_hping_activity.json} | 22 +- .../discovery_net_command_system_account.json | 47 - .../prepackaged_rules/discovery_net_view.json | 4 +- .../discovery_peripheral_device.json | 6 +- ...scovery_posh_suspicious_api_functions.json | 4 +- ..._post_exploitation_external_ip_lookup.json | 17 +- ...very_privileged_localgroup_membership.json | 4 +- ...ote_system_discovery_commands_windows.json | 11 +- .../discovery_security_software_wmic.json | 4 +- .../discovery_whoami_command_activity.json | 7 +- ...d_to_google_workspace_trusted_domains.json | 2 +- ...tion_abnormal_process_id_file_created.json | 54 + .../execution_apt_binary.json | 56 - ...arwinds_backdoor_child_cmd_powershell.json | 4 +- ...inds_backdoor_unusual_child_processes.json | 4 +- .../execution_awk_binary_shell.json | 56 - .../execution_busybox_binary.json | 55 - .../execution_c89_c99_binary.json | 56 - ...tion_command_shell_started_by_svchost.json | 2 +- .../execution_cpulimit_binary.json | 55 - .../execution_crash_binary.json | 55 - .../execution_env_binary.json | 55 - .../execution_expect_binary.json | 55 - .../execution_find_binary.json | 55 - .../execution_flock_binary.json | 55 - .../execution_gcc_binary.json | 55 - .../execution_ms_office_written_file.json | 3 +- .../execution_mysql_binary.json | 55 - .../execution_pdf_written_file.json | 5 +- .../execution_posh_portable_executable.json | 4 +- .../execution_posh_psreflect.json | 4 +- ..._process_started_from_process_id_file.json | 54 + ...ss_started_in_shared_memory_directory.json | 52 + .../execution_python_tty_shell.json | 2 +- .../execution_shell_evasion_linux_binary.json | 80 ++ .../execution_ssh_binary.json | 55 - .../execution_suspicious_pdf_reader.json | 4 +- ...ecution_suspicious_powershell_imgload.json | 4 +- .../execution_vi_binary.json | 55 - .../execution_via_hidden_shell_conhost.json | 4 +- .../google_workspace_admin_role_deletion.json | 2 +- ...le_workspace_mfa_enforcement_disabled.json | 2 +- .../google_workspace_policy_modified.json | 2 +- .../impact_backup_file_deletion.json | 4 +- ...deleting_backup_catalogs_with_wbadmin.json | 4 +- .../impact_hosts_file_modified.json | 2 +- .../impact_modification_of_boot_config.json | 4 +- ...impact_stop_process_service_threshold.json | 4 +- ...copy_deletion_or_resized_via_vssadmin.json | 4 +- ...e_shadow_copy_deletion_via_powershell.json | 4 +- ..._volume_shadow_copy_deletion_via_wmic.json | 4 +- .../rules/prepackaged_rules/index.ts | 1220 ++++++++--------- .../initial_access_password_recovery.json | 4 +- ...al_access_script_executing_powershell.json | 4 +- ...ss_suspicious_ms_office_child_process.json | 4 +- ...movement_executable_tool_transfer_smb.json | 17 +- ...lateral_movement_rdp_enabled_registry.json | 4 +- ...nux_process_started_in_temp_directory.json | 4 +- ...led_for_google_workspace_organization.json | 2 +- .../ml_linux_anomalous_compiler_activity.json | 6 +- ...nux_anomalous_kernel_module_arguments.json | 51 - .../ml_linux_anomalous_metadata_process.json | 5 +- .../ml_linux_anomalous_metadata_user.json | 5 +- .../ml_linux_anomalous_network_activity.json | 6 +- ...linux_anomalous_network_port_activity.json | 5 +- .../ml_linux_anomalous_network_service.json | 30 - ..._linux_anomalous_network_url_activity.json | 30 - .../ml_linux_anomalous_process_all_hosts.json | 5 +- .../ml_linux_anomalous_sudo_activity.json | 6 +- .../ml_linux_anomalous_user_name.json | 5 +- ...ml_linux_system_information_discovery.json | 6 +- ...ystem_network_configuration_discovery.json | 6 +- ...x_system_network_connection_discovery.json | 6 +- .../ml_linux_system_process_discovery.json | 6 +- .../ml_linux_system_user_discovery.json | 6 +- .../ml_rare_process_by_host_linux.json | 5 +- .../ml_rare_process_by_host_windows.json | 5 +- .../ml_suspicious_login_activity.json | 7 +- ...ml_windows_anomalous_metadata_process.json | 5 +- .../ml_windows_anomalous_metadata_user.json | 5 +- ...ml_windows_anomalous_network_activity.json | 5 +- .../ml_windows_anomalous_path_activity.json | 5 +- ...l_windows_anomalous_process_all_hosts.json | 5 +- ...ml_windows_anomalous_process_creation.json | 5 +- .../ml_windows_anomalous_script.json | 6 +- .../ml_windows_anomalous_service.json | 6 +- .../ml_windows_anomalous_user_name.json | 5 +- .../ml_windows_rare_user_runas_event.json | 6 +- ...windows_rare_user_type10_remote_login.json | 6 +- .../persistence_adobe_hijack_persistence.json | 4 +- .../persistence_crontab_creation.json | 55 + .../persistence_dontexpirepasswd_account.json | 5 +- ...persistence_emond_rules_file_creation.json | 7 +- ...evasion_hidden_local_account_creation.json | 4 +- ...egistry_startup_shell_folder_modified.json | 4 +- ...workspace_admin_role_assigned_to_user.json | 2 +- ...a_domain_wide_delegation_of_authority.json | 2 +- ...e_workspace_custom_admin_role_created.json | 2 +- ...stence_google_workspace_role_modified.json | 2 +- ...escalation_via_accessibility_features.json | 4 +- ...ersistence_redshift_instance_creation.json | 48 + .../persistence_registry_uncommon.json | 2 +- .../persistence_route_table_created.json | 4 +- ...tence_route_table_modified_or_deleted.json | 4 +- ...persistence_run_key_and_startup_broad.json | 2 +- ...istence_sdprop_exclusion_dsheuristics.json | 6 +- ...ence_ssh_authorized_keys_modification.json | 4 +- ...er_file_written_by_suspicious_process.json | 4 +- ...lder_file_written_by_unsigned_process.json | 4 +- .../persistence_startup_folder_scripts.json | 4 +- ...stence_suspicious_com_hijack_registry.json | 5 +- ...ersistence_system_shells_via_services.json | 4 +- ..._account_added_to_privileged_group_ad.json | 4 +- .../persistence_user_account_creation.json | 4 +- ...ege_escalation_group_policy_iniscript.json | 4 +- ...lation_group_policy_privileged_groups.json | 4 +- ...scalation_group_policy_scheduled_task.json | 6 +- ...rivilege_escalation_installertakeover.json | 4 +- ...scalation_krbrelayup_service_creation.json | 73 + ...scalation_krbrelayup_suspicious_logon.json | 72 + ...ge_escalation_persistence_phantom_dll.json | 4 +- ...printspooler_suspicious_file_deletion.json | 5 +- ...alation_suspicious_dnshostname_update.json | 61 + ...ion_unusual_printspooler_childprocess.json | 5 +- 200 files changed, 1906 insertions(+), 1882 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json rename x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/{linux_hping_activity.json => discovery_linux_hping_activity.json} (69%) delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_awk_binary_shell.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_busybox_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_c89_c99_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_cpulimit_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_crash_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_env_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_expect_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_find_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_flock_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_gcc_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mysql_binary.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_from_process_id_file.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_in_shared_memory_directory.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_evasion_linux_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ssh_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_vi_binary.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_kernel_module_arguments.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_service.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_url_activity.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_crontab_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_redshift_instance_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_suspicious_logon.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json index edbf848d78106..a8a4fc9deef35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/application_added_to_google_workspace_domain.json @@ -33,5 +33,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } 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 225ee22682e7c..7f46d666415f5 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 @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Exporting Exchange Mailbox via PowerShell", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Exporting Exchange Mailbox via PowerShell\n\nThe `New-MailBoxExportRequest` cmdlet is used to begin the process of exporting contents of a primary mailbox or archive\nto a .pst file. Note that this is done on a per-mailbox basis and this cmdlet is available only in on-premises Exchange.\n\nAttackers can abuse this functionality in preparation for exfiltrating contents, which is likely to contain sensitive\nand strategic data.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate the export operation:\n - Identify the user account that performed the action and whether it should perform this kind of action.\n - Contact the account owner and confirm whether they are aware of this activity.\n - Check if this operation is done under change management and approved according to the organization's policy.\n - Retrieve the operation status and use the `Get-MailboxExportRequest` cmdlet to review previous requests.\n - By default, no group in Exchange has the privilege to import or export mailboxes. Investigate administrators that\n assigned the \"Mailbox Import Export\" privilege for abnormal activity.\n- Investigate if there is a significant quantity of export requests in the alert timeframe. This operation is done on\na per-mailbox basis and can be part of a mass export.\n- If the operation was completed successfully:\n - Check if the file is on the path specified in the command.\n - Investigate if the file was compressed, archived, or retrieved by the attacker for exfiltration.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity\nand it is done with proper approval.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If the involved host is not the Exchange server, isolate the host to prevent further post-compromise behavior.\n- Use the `Remove-MailboxExportRequest` cmdlet to remove fully or partially completed export requests.\n- Prioritize cases that involve personally identifiable information (PII) or other classified data.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges of users with the \"Mailbox Import Export\" privilege to ensure that the least privilege principle\nis being followed.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name: (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") and process.args : \"New-MailboxExportRequest*\"\n", "references": [ "https://www.volexity.com/blog/2020/12/14/dark-halo-leverages-solarwinds-compromise-to-breach-organizations/", @@ -62,5 +62,5 @@ ], "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/collection_microsoft_365_new_inbox_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json index d989c53a79cf0..cd297c5adfd75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "Microsoft 365 Inbox Forwarding Rule Created", "note": "## Config\n\nThe Office 365 Logs 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\nevent.category:web and event.action:\"New-InboxRule\" and\n (\n o365audit.Parameters.ForwardTo:* or\n o365audit.Parameters.ForwardAsAttachmentTo:* or\n o365audit.Parameters.RedirectTo:*\n ) \n and event.outcome:success\n", + "query": "event.dataset:o365.audit and event.provider:Exchange and\nevent.category:web and event.action:\"New-InboxRule\" and\n (\n o365.audit.Parameters.ForwardTo:* or\n o365.audit.Parameters.ForwardAsAttachmentTo:* or\n o365.audit.Parameters.RedirectTo:*\n ) \n and event.outcome:success\n", "references": [ "https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/responding-to-a-compromised-email-account?view=o365-worldwide", "https://docs.microsoft.com/en-us/powershell/module/exchange/new-inboxrule?view=exchange-ps", @@ -61,5 +61,5 @@ ], "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/collection_posh_audio_capture.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json index 7534dbca7b9db..8083c0fb776b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Script with Audio Capture Capabilities", - "note": "## Triage and analysis.\n\n### Investigating PowerShell Suspicious Script with Audio Capture Capabilities\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell to interact with the Windows API with the intent of capturing audio from input devices\nconnected to the victim's computer.\n\n#### Possible investigation steps\n\n- Examine script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Investigate if the script stores the recorded data locally and determine if anything was recorded.\n- Investigate if the script contains exfiltration capabilities and the destination of this exfiltration.\n- Assess network data to determine if the host communicated with the exfiltration server.\n- Determine if the user credentials were compromised and if the attacker used them to perform unauthorized access to the\nlinked email account.\n\n### False positive analysis\n\n- Regular users should not need scripts to capture audio, which makes false positives unlikely. In the case of\nauthorized benign true positives (B-TPs), exceptions can be added.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The response must be prioritized if this alert involves key executives or potentially valuable targets for espionage.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n- Review GPOs to add additional restrictions for PowerShell usage by users.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Script with Audio Capture Capabilities\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell to interact with the Windows API with the intent of capturing audio from input devices\nconnected to the victim's computer.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate if the script stores the recorded data locally and determine if anything was recorded.\n- Investigate if the script contains exfiltration capabilities and the destination of this exfiltration.\n- Assess network data to determine if the host communicated with the exfiltration server.\n\n### False positive analysis\n\n- Regular users should not need scripts to capture audio, which makes false positives unlikely. In the case of\nauthorized benign true positives (B-TPs), exceptions can be added.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- The response must be prioritized if this alert involves key executives or potentially valuable targets for espionage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n \"Get-MicrophoneAudio\" or (waveInGetNumDevs and mciSendStringA)\n )\n", "references": [ "https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-MicrophoneAudio.ps1" @@ -67,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json index b3b9299fe0977..29bf64cfbf834 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Keylogging Script", - "note": "## Triage and analysis.\n\n### Investigating PowerShell Keylogging Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell capabilities to capture user keystrokes with the goal of stealing credentials and other\nvaluable information as credit card data and confidential conversations.\n\n#### Possible investigation steps:\n\n- Examine script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Investigate if the script stores the captured data locally.\n- Investigate if the script contains exfiltration capabilities and the destination of this exfiltration.\n- Assess network data to determine if the host communicated with the exfiltration server.\n\n### False positive analysis\n\n- Regular users do not have a business justification for using scripting utilities to capture keystrokes, making\nfalse positives unlikely. In the case of authorized benign true positives (B-TPs), exceptions can be added.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The response must be prioritized if this alert involves key executives or potentially valuable targets for espionage.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n- Reset the password for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell Keylogging Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell capabilities to capture user keystrokes with the goal of stealing credentials and other\nvaluable information as credit card data and confidential conversations.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate if the script stores the captured data locally.\n- Investigate if the script contains exfiltration capabilities and the destination of this exfiltration.\n- Assess network data to determine if the host communicated with the exfiltration server.\n\n### False positive analysis\n\n- Regular users do not have a business justification for using scripting utilities to capture keystrokes, making\nfalse positives unlikely. In the case of authorized benign true positives (B-TPs), exceptions can be added.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- The response must be prioritized if this alert involves key executives or potentially valuable targets for espionage.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n ( \n powershell.file.script_block_text : (GetAsyncKeyState or NtUserGetAsyncKeyState or GetKeyboardState or \"Get-Keystrokes\") or \n powershell.file.script_block_text : (\n (SetWindowsHookA or SetWindowsHookW or SetWindowsHookEx or SetWindowsHookExA or NtUserSetWindowsHookEx) and\n (GetForegroundWindow or GetWindowTextA or GetWindowTextW or \"WM_KEYBOARD_LL\")\n )\n )\n", "references": [ "https://github.com/EmpireProject/Empire/blob/master/data/module_source/collection/Get-Keystrokes.ps1", @@ -75,5 +75,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/collection_posh_screen_grabber.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json index 66e6dc5ec75f9..98c694a7f9ab4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Script with Screenshot Capabilities", - "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Script with Screenshot Capabilities\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, which makes\nit available for use in various environments and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell capabilities and take screen captures of desktops to gather information over the course\nof an operation.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts associated with the user or host in the past 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Investigate if the script stores the captured data locally.\n- Investigate if the script contains exfiltration capabilities and the destination of this exfiltration.\n- Examine network data to determine if the host communicated with the exfiltration server.\n\n### False positive analysis\n\n- Regular users do not have a business justification for using scripting utilities to take screenshots, which makes false\npositives unlikely. In the case of authorized benign true positives (B-TPs), exceptions can be added.\n\n### Related rules\n\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n- Reset the password for the user account.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Script with Screenshot Capabilities\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, which makes\nit available for use in various environments and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell capabilities and take screen captures of desktops to gather information over the course\nof an operation.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate if the script stores the captured data locally.\n- Investigate if the script contains exfiltration capabilities and the destination of this exfiltration.\n- Assess network data to determine if the host communicated with the exfiltration server.\n\n### False positive analysis\n\n- Regular users do not have a business justification for using scripting utilities to take screenshots, which makes false\npositives unlikely. In the case of authorized benign true positives (B-TPs), exceptions can be added.\n\n### Related rules\n\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n CopyFromScreen and\n (\"System.Drawing.Bitmap\" or \"Drawing.Bitmap\")\n )\n", "references": [ "https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics.copyfromscreen" @@ -67,5 +67,5 @@ ], "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/collection_winrar_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json index 3098d574d99c8..9b4fb9e87beb4 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Encrypting Files with WinRar or 7z", - "note": "## Triage and analysis\n\n### Investigating Encrypting Files with WinRar or 7z\n\nAttackers may compress and/or encrypt data collected before exfiltration. Compressing the data can help obfuscate the\ncollected data and minimize the amount of data sent over the network. Encryption can be used to hide information that is\nbeing exfiltrated from detection or make exfiltration less apparent upon inspection by a defender.\n\nThese steps are usually done in preparation for exfiltration, meaning the attack may be in its final stages.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Retrieve the encrypted file.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check if the password used in the encryption was included in the command line.\n- Decrypt the `.rar`/`.zip` and check if the information is sensitive.\n- If the password is not available, and the format is `.zip` or the option used in WinRAR is not the `-hp`, list the\nfile names included in the encrypted file.\n- Investigate if the file was transferred to an attacker-controlled server.\n\n### False positive analysis\n\n- Backup software can use these utilities. Check the `process.parent.executable` and\n`process.parent.command_line` fields to determine what triggered the encryption.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If personally identifiable information (PII) or other classified data is involved, investigations into this should be prioritized.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n- Reset the passwords of the involved accounts.\n- Safeguard critical assets to prevent further harm or theft of data.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Encrypting Files with WinRar or 7z\n\nAttackers may compress and/or encrypt data collected before exfiltration. Compressing the data can help obfuscate the\ncollected data and minimize the amount of data sent over the network. Encryption can be used to hide information that is\nbeing exfiltrated from detection or make exfiltration less apparent upon inspection by a defender.\n\nThese steps are usually done in preparation for exfiltration, meaning the attack may be in its final stages.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the encrypted file.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if the password used in the encryption was included in the command line.\n- Decrypt the `.rar`/`.zip` and check if the information is sensitive.\n- If the password is not available, and the format is `.zip` or the option used in WinRAR is not the `-hp`, list the\nfile names included in the encrypted file.\n- Investigate if the file was transferred to an attacker-controlled server.\n\n### False positive analysis\n\n- Backup software can use these utilities. Check the `process.parent.executable` and\n`process.parent.command_line` fields to determine what triggered the encryption.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Prioritize cases that involve personally identifiable information (PII) or other classified data.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n ((process.name:\"rar.exe\" or process.code_signature.subject_name == \"win.rar GmbH\" or\n process.pe.original_file_name == \"Command line RAR\") and\n process.args == \"a\" and process.args : (\"-hp*\", \"-p*\", \"-dw\", \"-tb\", \"-ta\", \"/hp*\", \"/p*\", \"/dw\", \"/tb\", \"/ta\"))\n\n or\n (process.pe.original_file_name in (\"7z.exe\", \"7za.exe\") and\n process.args == \"a\" and process.args : (\"-p*\", \"-sdel\"))\n\n /* uncomment if noisy for backup software related FPs */\n /* not process.parent.executable : (\"C:\\\\Program Files\\\\*.exe\", \"C:\\\\Program Files (x86)\\\\*.exe\") */\n", "references": [ "https://www.welivesecurity.com/2020/12/02/turla-crutch-keeping-back-door-open/" @@ -53,5 +53,5 @@ ], "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/command_and_control_certutil_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json index 3fa17ac4585cb..e3c9edaf003dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Network Connection via Certutil", - "note": "## Triage and analysis\n\n### Investigating Network Connection via Certutil\n\nAttackers can abuse `certutil.exe` to download malware, offensive security tools, and certificates from external sources\nin order to take the next steps in a compromised environment.\n\nThis rule looks for network events where `certutil.exe` contacts IP ranges other than the ones specified in\n[IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml)\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Investigate if the downloaded file was executed.\n- Determine the context in which `certutil.exe` and the file were run.\n- Retrieve the file downloaded and:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts of contacting external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. If trusted software uses this command and the triage has not identified\nanything suspicious, this alert can be closed as a false positive.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n- Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n", + "note": "## Triage and analysis\n\n### Investigating Network Connection via Certutil\n\nAttackers can abuse `certutil.exe` to download malware, offensive security tools, and certificates from external sources\nin order to take the next steps in a compromised environment.\n\nThis rule looks for network events where `certutil.exe` contacts IP ranges other than the ones specified in\n[IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml)\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate if the downloaded file was executed.\n- Determine the context in which `certutil.exe` and the file were run.\n- Retrieve the downloaded file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. If trusted software uses this command and the triage has not identified\nanything suspicious, this alert can be closed as a false positive.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by process.entity_id\n [process where process.name : \"certutil.exe\" and event.type == \"start\"]\n [network where process.name : \"certutil.exe\" and\n not cidrmatch(destination.ip, \"10.0.0.0/8\", \"127.0.0.0/8\", \"169.254.0.0/16\", \"172.16.0.0/12\", \"192.0.0.0/24\",\n \"192.0.0.0/29\", \"192.0.0.8/32\", \"192.0.0.9/32\", \"192.0.0.10/32\", \"192.0.0.170/32\",\n \"192.0.0.171/32\", \"192.0.2.0/24\", \"192.31.196.0/24\", \"192.52.193.0/24\",\n \"192.168.0.0/16\", \"192.88.99.0/24\", \"224.0.0.0/4\", \"100.64.0.0/10\", \"192.175.48.0/24\",\n \"198.18.0.0/15\", \"198.51.100.0/24\", \"203.0.113.0/24\", \"240.0.0.0/4\", \"::1\",\n \"FE80::/10\", \"FF00::/8\")]\n", "references": [ "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml", @@ -46,5 +46,5 @@ } ], "type": "eql", - "version": 7 + "version": 8 } 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 540babaeea673..1b7b241c7febd 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 @@ -10,8 +10,8 @@ "language": "eql", "license": "Elastic License v2", "name": "Connection to Commonly Abused Web Services", - "note": "## Triage and analysis\n\n### Investigating Connection to Commonly Abused Web Services\n\nAdversaries may use an existing, legitimate external Web service as a means for relaying data to/from a compromised\nsystem. Popular websites and social media acting as a mechanism for C2 may give a significant amount of cover due to the\nlikelihood that hosts within a network are already communicating with them prior to a compromise.\n\nThis rule looks for processes outside known legitimate program locations communicating with a list of services that can\nbe abused for exfiltration or command and control.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Verify whether the digital signature exists in the executable.\n- Identify the kind of the operation (upload, download, tunneling, etc.).\n- Use a sandboxed malware analysis system to perform analysis on the executable.\n- Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives because it detects communication with legitimate services. Noisy\nfalse positives can be added as exceptions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n- Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n", - "query": "network where network.protocol == \"dns\" and\n process.name != null and user.id not in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"raw.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\",\n \"rawcdn.githack.*\",\n \"paste.nrecom.net\",\n \"zerobin.net\",\n \"controlc.com\",\n \"requestbin.net\",\n \"cdn.discordapp.com\",\n \"discordapp.com\",\n \"discord.com\"\n ) and\n /* Insert noisy false positives here */\n not process.executable :\n (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\WWAHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\smartscreen.exe\",\n \"?:\\\\Windows\\\\System32\\\\MicrosoftEdgeCP.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Fiddler\\\\Fiddler.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Microsoft VS Code\\\\Code.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\OneDrive.exe\",\n \"?:\\\\Windows\\\\system32\\\\mobsync.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\mobsync.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Discord\\\\-*\\\\Discord.exe\"\n )\n", + "note": "## Triage and analysis\n\n### Investigating Connection to Commonly Abused Web Services\n\nAdversaries may use an existing, legitimate external Web service as a means for relaying data to/from a compromised\nsystem. Popular websites and social media acting as a mechanism for C2 may give a significant amount of cover due to the\nlikelihood that hosts within a network are already communicating with them prior to a compromise.\n\nThis rule looks for processes outside known legitimate program locations communicating with a list of services that can\nbe abused for exfiltration or command and control.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Verify whether the digital signature exists in the executable.\n- Identify the operation type (upload, download, tunneling, etc.).\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives because it detects communication with legitimate services. Noisy\nfalse positives can be added as exceptions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", + "query": "network where network.protocol == \"dns\" and\n process.name != null and user.id not in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"raw.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\",\n \"rawcdn.githack.*\",\n \"paste.nrecom.net\",\n \"zerobin.net\",\n \"controlc.com\",\n \"requestbin.net\",\n \"cdn.discordapp.com\",\n \"discordapp.com\",\n \"discord.com\"\n ) and\n /* Insert noisy false positives here */\n not process.executable :\n (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\WWAHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\smartscreen.exe\",\n \"?:\\\\Windows\\\\System32\\\\MicrosoftEdgeCP.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Fiddler\\\\Fiddler.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Microsoft VS Code\\\\Code.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\OneDrive.exe\",\n \"?:\\\\Windows\\\\system32\\\\mobsync.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\mobsync.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Discord\\\\app-*\\\\Discord.exe\"\n )\n", "risk_score": 21, "rule_id": "66883649-f908-4a5b-a1e0-54090a1d3a32", "severity": "low", @@ -68,5 +68,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/command_and_control_connection_attempt_by_non_ssh_root_session.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json new file mode 100644 index 0000000000000..7cdba833a00e5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json @@ -0,0 +1,74 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies an outbound network connection attempt followed by a session id change as the root user by the same process entity. This particular instantiation of a network connection is abnormal and should be investigated as it may indicate a potential reverse shell activity via a privileged process.", + "false_positives": [ + "False-Positives (FP) can appear if another remote terminal service is being used to connect to it's listener but typically SSH is used in these scenarios." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Suspicious Network Connection Attempt by Root", + "note": "## Triage and analysis\n### Investigating Connection Attempt by Non-SSH Root Session\nDetection alerts from this rule indicate a strange or abnormal outbound connection attempt by a privileged process. Here are some possible avenues of investigation:\n- Examine unusual and active sessions using commands such as 'last -a', 'netstat -a', and 'w -a'.\n- Analyze processes and command line arguments to detect anomalous process execution that may be acting as a listener.\n- Analyze anomalies in the use of files that do not normally initiate connections.\n- Examine processes utilizing the network that do not normally have network communication.\n", + "query": "sequence by process.entity_id with maxspan=1m\n[network where event.type == \"start\" and event.action == \"connection_attempted\" and user.id == \"0\" and \n not process.executable : (\"/bin/ssh\", \"/sbin/ssh\", \"/usr/lib/systemd/systemd\")]\n[process where event.action == \"session_id_change\" and user.id == \"0\"]\n", + "references": [ + "https://www.sandflysecurity.com/blog/linux-file-masquerading-and-malicious-pids-sandfly-1-2-6-update/", + "https://twitter.com/GossiTheDog/status/1522964028284411907", + "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf" + ], + "risk_score": 43, + "rule_id": "eb6a3790-d52d-11ec-8ce9-f661ea17fbce", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Command and Control" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0011", + "name": "Command and Control", + "reference": "https://attack.mitre.org/tactics/TA0011/" + }, + "technique": [ + { + "id": "T1095", + "name": "Non-Application Layer Protocol", + "reference": "https://attack.mitre.org/techniques/T1095/" + } + ] + }, + { + "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/", + "subtechnique": [ + { + "id": "T1548.003", + "name": "Sudo and Sudo Caching", + "reference": "https://attack.mitre.org/techniques/T1548/003/" + } + ] + } + ] + } + ], + "type": "eql", + "version": 1 +} 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 f9109b2d3ff0e..2a630beddd367 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 @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential DNS Tunneling via NsLookup", - "note": "## Triage and analysis\n\n### Investigating Potential DNS Tunneling via NsLookup\n\nAttackers can abuse existing network rules that allow DNS communication with external resources to use the protocol as\ntheir command and control and/or exfiltration channel.\n\nDNS queries can be used to infiltrate data such as commands to be run, malicious files, etc., and also for exfiltration,\nsince queries can be used to send data to the attacker-controlled DNS server. This process is commonly known as DNS tunneling.\n\nMore information on how tunneling works and how it can be abused can be found on\n[Palo Alto Unit42 Research](https://unit42.paloaltonetworks.com/dns-tunneling-how-dns-can-be-abused-by-malicious-actors).\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Inspect the DNS query and identify the information sent.\n- Extract this communication's indicators of compromise (IoCs) and use traffic logs to search for other potentially compromised hosts.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. If the parent process is trusted and the data sent is not sensitive nor\ncommand and control related, this alert can be closed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Immediately block the IoCs identified on the triage.\n- Implement any temporary network rules, procedures, and segmentation required to contain the attack.\n- Capture forensic images to preserve evidence.\n- Reset the password for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Update firewall rules to be more restrictive.\n- Reimage affected systems.\n", + "note": "## Triage and analysis\n\n### Investigating Potential DNS Tunneling via NsLookup\n\nAttackers can abuse existing network rules that allow DNS communication with external resources to use the protocol as\ntheir command and control and/or exfiltration channel.\n\nDNS queries can be used to infiltrate data such as commands to be run, malicious files, etc., and also for exfiltration,\nsince queries can be used to send data to the attacker-controlled DNS server. This process is commonly known as DNS tunneling.\n\nMore information on how tunneling works and how it can be abused can be found on\n[Palo Alto Unit42 Research](https://unit42.paloaltonetworks.com/dns-tunneling-how-dns-can-be-abused-by-malicious-actors).\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the DNS query and identify the information sent.\n- Extract this communication's indicators of compromise (IoCs) and use traffic logs to search for other potentially\ncompromised hosts.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. If the parent process is trusted and the data sent is not sensitive nor\ncommand and control related, this alert can be closed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Immediately block the identified indicators of compromise (IoCs).\n- Implement any temporary network rules, procedures, and segmentation required to contain the attack.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Update firewall rules to be more restrictive.\n- Reimage the host operating system or restore the compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.category:process and event.type:start and process.name:nslookup.exe and process.args:(-querytype=* or -qt=* or -q=* or -type=*)\n", "references": [ "https://unit42.paloaltonetworks.com/dns-tunneling-in-the-wild-overview-of-oilrigs-dns-tunneling/" @@ -58,5 +58,5 @@ "value": 15 }, "type": "threshold", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_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 index 1536791a3eb2d..9612afbc6c03d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Port Forwarding Rule Addition", - "note": "## Triage and analysis\n\n### Investigating Port Forwarding Rule Addition\n\nNetwork port forwarding is a mechanism to redirect incoming TCP connections (IPv4 or IPv6) from the local TCP port to\nany other port number, or even to a port on a remote computer.\n\nAttackers may configure port forwarding rules to bypass network segmentation restrictions, using the host as a jump box\nto access previously unreachable systems.\n\nThis rule monitors the modifications to the `HKLM\\SYSTEM\\*ControlSet*\\Services\\PortProxy\\v4tov4\\` subkeys.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Identify the target host IP address, verify if connections were made from the host where the modification occurred,\nand check what credentials were used to perform it.\n - Investigate suspicious login activity, such as unauthorized access and logins from outside working hours and unusual locations.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the Administrator is aware of the activity\nand there are justifications for this configuration.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Delete the port forwarding rule.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If potential malware or credential compromise activities were discovered during the alert triage, activate the respective\nincident response plan.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Port Forwarding Rule Addition\n\nNetwork port forwarding is a mechanism to redirect incoming TCP connections (IPv4 or IPv6) from the local TCP port to\nany other port number, or even to a port on a remote computer.\n\nAttackers may configure port forwarding rules to bypass network segmentation restrictions, using the host as a jump box\nto access previously unreachable systems.\n\nThis rule monitors the modifications to the `HKLM\\SYSTEM\\*ControlSet*\\Services\\PortProxy\\v4tov4\\` subkeys.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Identify the target host IP address, check the connections originating from the host where the modification occurred,\nand inspect the credentials used.\n - Investigate suspicious login activity, such as unauthorized access and logins from outside working hours and unusual locations.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the Administrator is aware of the activity\nand there are justifications for this configuration.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Delete the port forwarding rule.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\PortProxy\\\\v4tov4\\\\*\"\n", "references": [ "https://www.fireeye.com/blog/threat-research/2019/01/bypassing-network-restrictions-through-rdp-tunneling.html" @@ -46,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json index 7277998b00c51..1896e52b0c2b5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json @@ -76,5 +76,5 @@ "timeline_title": "Comprehensive Network Timeline", "timestamp_override": "event.ingested", "type": "query", - "version": 12 + "version": 14 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json index b3c2e6905ed9b..b9b909f03e272 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Remote Desktop Tunneling Detected", - "note": "## Triage and analysis\n\n### Investigating Potential Remote Desktop Tunneling Detected\n\nProtocol Tunneling is a mechanism that involves explicitly encapsulating a protocol within another for various use cases,\nranging from providing an outer layer of encryption (similar to a VPN) to enabling traffic that network appliances would\nfilter to reach their destination.\n\nAttackers may tunnel Remote Desktop Protocol (RDP) traffic through other protocols like Secure Shell (SSH) to bypass network restrictions that block incoming RDP\nconnections but may be more permissive to other protocols.\n\nThis rule looks for command lines involving the `3389` port, which RDP uses by default and options commonly associated\nwith tools that perform tunneling.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if the activity is unique by validating if other machines in the organization have similar entries.\n- Examine network data to determine if the host communicated with external servers using the tunnel.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n- Investigate the command line for the execution of programs that are unrelated to tunneling, like Remote Desktop clients.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Take actions to disable the tunneling.\n- Investigate the initial attack vector.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Potential Remote Desktop Tunneling Detected\n\nProtocol Tunneling is a mechanism that involves explicitly encapsulating a protocol within another for various use cases,\nranging from providing an outer layer of encryption (similar to a VPN) to enabling traffic that network appliances would\nfilter to reach their destination.\n\nAttackers may tunnel Remote Desktop Protocol (RDP) traffic through other protocols like Secure Shell (SSH) to bypass network restrictions that block incoming RDP\nconnections but may be more permissive to other protocols.\n\nThis rule looks for command lines involving the `3389` port, which RDP uses by default and options commonly associated\nwith tools that perform tunneling.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Examine network data to determine if the host communicated with external servers using the tunnel.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n- Investigate the command line for the execution of programs that are unrelated to tunneling, like Remote Desktop clients.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Take the necessary actions to disable the tunneling, which can be a process kill, service deletion, registry key\nmodification, etc. Inspect the host to learn which method was used and to determine a response for the case.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n /* RDP port and usual SSH tunneling related switches in command line */\n process.args : \"*:3389\" and\n process.args : (\"-L\", \"-P\", \"-R\", \"-pw\", \"-ssh\")\n", "references": [ "https://blog.netspi.com/how-to-access-rdp-over-a-reverse-ssh-tunnel/" @@ -46,5 +46,5 @@ ], "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/command_and_control_remote_file_copy_desktopimgdownldr.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json index 6707f4185e125..ab3e55a5822ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via Desktopimgdownldr Utility", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via Desktopimgdownldr Utility\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `Desktopimgdownldr.exe` utility is used to to configure lockscreen/desktop image, and can be abused with the\n`lockscreenurl` argument to download remote files and tools, this rule looks for this behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if the activity is unique by validating if other machines in the organization have similar entries.\n- Check the reputation of the domain or IP address used to host the downloaded file or if the user downloaded the file\nfrom an internal system.\n- Retrieve the file and determine if it is malicious:\n - Identify the file type.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unusual but can be done by administrators. Benign true positives (B-TPs) can be added as exceptions\nif necessary.\n- Analysts can dismiss the alert if the downloaded file is a legitimate image.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the IoCs identified.\n- Remove and block malicious artifacts identified on the triage.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Investigate the initial attack vector.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via Desktopimgdownldr Utility\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `Desktopimgdownldr.exe` utility is used to to configure lockscreen/desktop image, and can be abused with the\n`lockscreenurl` argument to download remote files and tools, this rule looks for this behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Check the reputation of the domain or IP address used to host the downloaded file or if the user downloaded the file\nfrom an internal system.\n- Retrieve the file and determine if it is malicious:\n - Identify the file type.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unusual but can be done by administrators. Benign true positives (B-TPs) can be added as exceptions\nif necessary.\n- Analysts can dismiss the alert if the downloaded file is a legitimate image.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"desktopimgdownldr.exe\" or process.pe.original_file_name == \"desktopimgdownldr.exe\") and\n process.args : \"/lockscreenurl:http*\"\n", "references": [ "https://labs.sentinelone.com/living-off-windows-land-a-new-native-file-downldr/" @@ -46,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json index eb5c0e07a534d..59e4e59a88c32 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via MpCmdRun", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via MpCmdRun\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `MpCmdRun.exe` is a command-line tool part of Windows Defender and is used to manage various Microsoft Windows\nDefender Antivirus settings and perform certain tasks. It can also be abused by attackers to download remote files,\nincluding malware and offensive tooling. This rule looks for the patterns used to perform downloads using the utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the IoCs identified.\n- Remove and block malicious artifacts identified on the triage.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Investigate the initial attack vector.\nVerify details such as the parent process, URL reputation, and downloaded file details. Additionally, `MpCmdRun` logs this information in the Appdata Temp folder in `MpCmdRun.log`.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via MpCmdRun\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nThe `MpCmdRun.exe` is a command-line tool part of Windows Defender and is used to manage various Microsoft Windows\nDefender Antivirus settings and perform certain tasks. It can also be abused by attackers to download remote files,\nincluding malware and offensive tooling. This rule looks for the patterns used to perform downloads using the utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type == \"start\" and\n (process.name : \"MpCmdRun.exe\" or process.pe.original_file_name == \"MpCmdRun.exe\") and\n process.args : \"-DownloadFile\" and process.args : \"-url\" and process.args : \"-path\"\n", "references": [ "https://twitter.com/mohammadaskar2/status/1301263551638761477", @@ -47,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json index 2f30ef0bf0421..2fbd3dffc5c14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Download via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via PowerShell\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nPowerShell is one of system administrators' main tools for automation, report routines, and other tasks. This makes it\navailable for use in various environments and creates an attractive way for attackers to execute code and perform\nactions. This rule correlates network and file events to detect downloads of executable and script files performed using\nPowerShell.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators can use PowerShell legitimately to download executable and script files. Analysts can dismiss the alert\nif the Administrator is aware of the activity and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the IoCs identified.\n- Remove and block malicious artifacts identified on the triage.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Investigate the initial attack vector.\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via PowerShell\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse signed utilities to drop these files.\n\nPowerShell is one of system administrators' main tools for automation, report routines, and other tasks. This makes it\navailable for use in various environments and creates an attractive way for attackers to execute code and perform\nactions. This rule correlates network and file events to detect downloads of executable and script files performed using\nPowerShell.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the reputation of the domain or IP address used to host the downloaded file.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators can use PowerShell legitimately to download executable and script files. Analysts can dismiss the alert\nif the Administrator is aware of the activity and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id, process.entity_id with maxspan=30s\n [network where process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") and network.protocol == \"dns\" and\n not dns.question.name : (\"localhost\", \"*.microsoft.com\", \"*.azureedge.net\", \"*.powershellgallery.com\", \"*.windowsupdate.com\", \"metadata.google.internal\") and \n not user.domain : \"NT AUTHORITY\"]\n [file where process.name : \"powershell.exe\" and event.type == \"creation\" and file.extension : (\"exe\", \"dll\", \"ps1\", \"bat\") and \n not file.name : \"__PSScriptPolicy*.ps1\"]\n", "risk_score": 47, "rule_id": "33f306e8-417c-411b-965c-c2812d6d3f4d", @@ -64,5 +64,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } 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 d6d74d8102ee5..d35d969ec4472 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", - "note": "## Triage and analysis\n\n### Investigating Remote File Download via Script Interpreter\n\nThe Windows Script Host (WSH) is a Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for DLLs and executables downloaded using `cscript.exe` or `wscript.exe`.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Retrieve the script file and the executable involved:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Manually analyze the script to determine if malicious capabilities are present.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check for similar behavior in other hosts on the environment.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n- Remove and block malicious artifacts identified on the triage.\n- Reimage the host operating system and restore compromised files to clean versions.\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Download via Script Interpreter\n\nThe Windows Script Host (WSH) is a Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for DLLs and executables downloaded using `cscript.exe` or `wscript.exe`.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the script file and the executable involved and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Manually analyze the script to determine if malicious capabilities are present.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\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", @@ -42,5 +42,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json index 2f08c107752ba..11eaab819b4ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "SUNBURST Command and Control Activity", - "note": "## Triage and analysis\n\n### Investigating SUNBURST Command and Control Activity\n\nSUNBURST is a trojanized version of a digitally signed SolarWinds Orion plugin called\nSolarWinds.Orion.Core.BusinessLayer.dll. The plugin contains a backdoor that communicates via HTTP to third-party\nservers. After an initial dormant period of up to two weeks, SUNBURST may retrieve and execute commands that instruct\nthe backdoor to transfer files, execute files, profile the system, reboot the system, and disable system services.\nThe malware's network traffic attempts to blend in with legitimate SolarWinds activity by imitating the Orion\nImprovement Program (OIP) protocol, and the malware stores persistent state data within legitimate plugin configuration files. The\nbackdoor uses multiple obfuscated blocklists to identify processes, services, and drivers associated with forensic and\nanti-virus tools.\n\nMore details on SUNBURST can be found on the [Mandiant Report](https://www.mandiant.com/resources/sunburst-additional-technical-details).\n\nThis rule identifies suspicious network connections that attempt to blend in with legitimate SolarWinds activity\nby imitating the Orion Improvement Program (OIP) protocol behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Retrieve the executable involved:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Manually analyze the executable to determine if malicious capabilities are present.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate the network traffic.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check for similar behavior in other hosts on the environment.\n\n### False positive analysis\n\n- False positives are unlikely for this rule.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n- Remove and block malicious artifacts identified on the triage.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Upgrade SolarWinds systems to the latest version.\n", + "note": "## Triage and analysis\n\n### Investigating SUNBURST Command and Control Activity\n\nSUNBURST is a trojanized version of a digitally signed SolarWinds Orion plugin called\nSolarWinds.Orion.Core.BusinessLayer.dll. The plugin contains a backdoor that communicates via HTTP to third-party\nservers. After an initial dormant period of up to two weeks, SUNBURST may retrieve and execute commands that instruct\nthe backdoor to transfer files, execute files, profile the system, reboot the system, and disable system services.\nThe malware's network traffic attempts to blend in with legitimate SolarWinds activity by imitating the Orion\nImprovement Program (OIP) protocol, and the malware stores persistent state data within legitimate plugin configuration files. The\nbackdoor uses multiple obfuscated blocklists to identify processes, services, and drivers associated with forensic and\nanti-virus tools.\n\nMore details on SUNBURST can be found on the [Mandiant Report](https://www.mandiant.com/resources/sunburst-additional-technical-details).\n\nThis rule identifies suspicious network connections that attempt to blend in with legitimate SolarWinds activity\nby imitating the Orion Improvement Program (OIP) protocol behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Retrieve the executable involved:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Investigate whether the potential malware ran successfully, is active on the host, or was stopped by defenses.\n- Investigate the network traffic.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the environment at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Upgrade SolarWinds systems to the latest version to eradicate the chance of reinfection by abusing the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "network where event.type == \"protocol\" and network.protocol == \"http\" and\n process.name : (\"ConfigurationWizard.exe\",\n \"NetFlowService.exe\",\n \"NetflowDatabaseMaintenance.exe\",\n \"SolarWinds.Administration.exe\",\n \"SolarWinds.BusinessLayerHost.exe\",\n \"SolarWinds.BusinessLayerHostx64.exe\",\n \"SolarWinds.Collector.Service.exe\",\n \"SolarwindsDiagnostics.exe\") and\n (http.request.body.content : \"*/swip/Upload.ashx*\" and http.request.body.content : (\"POST*\", \"PUT*\")) or\n (http.request.body.content : (\"*/swip/SystemDescription*\", \"*/swip/Events*\") and http.request.body.content : (\"GET*\", \"HEAD*\")) and\n not http.request.body.content : \"*solarwinds.com*\"\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html" @@ -73,5 +73,5 @@ ], "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/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 200c5ebe48356..e14d72cc0f03a 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote File Copy via TeamViewer", - "note": "## Triage and analysis\n\n### Investigating Remote File Copy via TeamViewer\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse legitimate utilities to drop these files.\n\nTeamViewer is a remote access and remote control tool used by helpdesks and system administrators to perform various\nsupport activities. It is also frequently used by attackers and scammers to deploy malware interactively and other\nmalicious activities. This rule looks for the TeamViewer process creating files with suspicious extensions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Contact the user to gather information about who and why was conducting the remote access.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether the company uses TeamViewer for the support activities and if there is a support ticket related to this\naccess.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the company relies on TeamViewer to conduct\nremote access and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the IoCs identified.\n- Remove and block malicious artifacts identified on the triage.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Remote File Copy via TeamViewer\n\nAttackers commonly transfer tooling or malware from external systems into a compromised environment using the command\nand control channel. However, they can also abuse legitimate utilities to drop these files.\n\nTeamViewer is a remote access and remote control tool used by helpdesks and system administrators to perform various\nsupport activities. It is also frequently used by attackers and scammers to deploy malware interactively and other\nmalicious activities. This rule looks for the TeamViewer process creating files with suspicious extensions.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Contact the user to gather information about who and why was conducting the remote access.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether the company uses TeamViewer for the support activities and if there is a support ticket related to this\naccess.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the company relies on TeamViewer to conduct\nremote access and the triage has not identified suspicious or malicious files.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "file where event.type == \"creation\" and process.name : \"TeamViewer.exe\" and\n file.extension : (\"exe\", \"dll\", \"scr\", \"com\", \"bat\", \"ps1\", \"vbs\", \"vbe\", \"js\", \"wsh\", \"hta\")\n", "references": [ "https://blog.menasec.net/2019/11/hunting-for-suspicious-use-of.html" @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json index 3487f46c1d855..e9e39f92954f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json @@ -73,5 +73,5 @@ "timeline_title": "Comprehensive Network Timeline", "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json index 97ca284b78912..e1ef3f255d4e5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Credential Access via DCSync", - "note": "## Triage and analysis\n\n### Investigating Potential Credential Access via DCSync\n\nActive Directory replication is the process by which the changes that originate on one domain controller are\nautomatically transferred to other domain controllers that store the same data.\n\nActive Directory data consists of objects that have properties, or attributes. Each object is an instance of an object\nclass, and object classes and their respective attributes are defined in the Active Directory schema. Objects are\ndefined by the values of their attributes, and changes to attribute values must be transferred from the domain\ncontroller on which they occur to every other domain controller that stores a replica of an affected object. \n\nAdversaries can use the DCSync technique that uses Windows Domain Controller's API to simulate the replication process\nfrom a remote domain controller, compromising major credential material such as the Kerberos krbtgt keys used\nlegitimately for tickets creation, but also tickets forging by attackers. This attack requires some extended privileges\nto succeed (DS-Replication-Get-Changes and DS-Replication-Get-Changes-All), which are granted by default to members of\nthe Administrators, Domain Admins, Enterprise Admins, and Domain Controllers groups. Privileged accounts can be abused\nto grant controlled objects the right to DCsync/Replicate.\n\nMore details can be found on [Threat Hunter Playbook](https://threathunterplaybook.com/library/windows/active_directory_replication.html?highlight=dcsync#directory-replication-services-auditing) and [The Hacker Recipes](https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync).\n\nThis rule monitors for Event ID 4662 (Operation was performed on an Active Directory object) and identifies events that\nuse the access mask 0x100 (Control Access) and properties that contain at least one of the following or their equivalent:\nSchema-Id-GUID (DS-Replication-Get-Changes, DS-Replication-Get-Changes-All, DS-Replication-Get-Changes-In-Filtered-Set).\nIt also filters out events that use computer accounts and also Azure AD Connect MSOL accounts (more details [here](https://techcommunity.microsoft.com/t5/microsoft-defender-for-identity/ad-connect-msol-user-suspected-dcsync-attack/m-p/788028)). \n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Correlate security events 4662 and 4624 (Logon Type 3) by their Logon ID (`winlog.logon.id`) on the Domain Controller\n(DC) that received the replication request. This will tell you where the AD replication request came from, and if it\ncame from another DC or not.\n- Investigate which credentials were compromised (for example whether all accounts were replicated, or only a specific account).\n\n### False positive analysis \n\n- This activity should not happen legitimately, since replication should be done by Domain Controllers only. Any\npotential benign true positive (B-TP) should be mapped and monitored by the security team. Any account that performs\nthis activity can put the domain at risk for not having the same security standards as computer accounts (which have\nlong, complex, random passwords that change frequently), exposing it to credential cracking attacks (Kerberoasting,\nbrute force, etc.).\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If specific credentials were compromised:\n - Reset passwords for affected accounts.\n- If the entire domain or the `krbtgt` user were compromised:\n - Activate your incident response plan for total Active Directory compromise which should include, but not be limited\n to, a password reset (twice) of the `krbtgt` user. \n\n## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nDS Access >\nAudit Directory Service Changes (Success,Failure)\n```\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Potential Credential Access via DCSync\n\nActive Directory replication is the process by which the changes that originate on one domain controller are\nautomatically transferred to other domain controllers that store the same data.\n\nActive Directory data consists of objects that have properties, or attributes. Each object is an instance of an object\nclass, and object classes and their respective attributes are defined in the Active Directory schema. Objects are\ndefined by the values of their attributes, and changes to attribute values must be transferred from the domain\ncontroller on which they occur to every other domain controller that stores a replica of an affected object. \n\nAdversaries can use the DCSync technique that uses Windows Domain Controller's API to simulate the replication process\nfrom a remote domain controller, compromising major credential material such as the Kerberos krbtgt keys used\nlegitimately for tickets creation, but also tickets forging by attackers. This attack requires some extended privileges\nto succeed (DS-Replication-Get-Changes and DS-Replication-Get-Changes-All), which are granted by default to members of\nthe Administrators, Domain Admins, Enterprise Admins, and Domain Controllers groups. Privileged accounts can be abused\nto grant controlled objects the right to DCsync/Replicate.\n\nMore details can be found on [Threat Hunter Playbook](https://threathunterplaybook.com/library/windows/active_directory_replication.html?highlight=dcsync#directory-replication-services-auditing) and [The Hacker Recipes](https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync).\n\nThis rule monitors for Event ID 4662 (Operation was performed on an Active Directory object) and identifies events that\nuse the access mask 0x100 (Control Access) and properties that contain at least one of the following or their equivalent:\nSchema-Id-GUID (DS-Replication-Get-Changes, DS-Replication-Get-Changes-All, DS-Replication-Get-Changes-In-Filtered-Set).\nIt also filters out events that use computer accounts and also Azure AD Connect MSOL accounts (more details [here](https://techcommunity.microsoft.com/t5/microsoft-defender-for-identity/ad-connect-msol-user-suspected-dcsync-attack/m-p/788028)). \n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Correlate security events 4662 and 4624 (Logon Type 3) by their Logon ID (`winlog.logon.id`) on the Domain Controller\n(DC) that received the replication request. This will tell you where the AD replication request came from, and if it\ncame from another DC or not.\n- Scope which credentials were compromised (for example, whether all accounts were replicated or specific ones).\n\n### False positive analysis \n\n- This activity should not happen legitimately, since replication should be done by Domain Controllers only. Any\npotential benign true positive (B-TP) should be mapped and monitored by the security team. Any account that performs\nthis activity can put the domain at risk for not having the same security standards as computer accounts (which have\nlong, complex, random passwords that change frequently), exposing it to credential cracking attacks (Kerberoasting,\nbrute force, etc.).\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If specific credentials were compromised:\n - Reset the password for these accounts and other potentially compromised credentials, like email, business systems,\n and web services.\n- If the entire domain or the `krbtgt` user were compromised:\n - Activate your incident response plan for total Active Directory compromise which should include, but not be limited\n to, a password reset (twice) of the `krbtgt` user.\n- Investigate how the attacker escalated privileges and identify systems they used to conduct lateral movement. Use this\ninformation to scope ways that the attacker could use to regain access to the environment.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nDS Access >\nAudit Directory Service Changes (Success,Failure)\n```\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "any where event.action == \"Directory Service Access\" and\n event.code == \"4662\" and winlog.event_data.Properties : (\n\n /* Control Access Rights/Permissions Symbol */\n\n \"*DS-Replication-Get-Changes*\",\n \"*DS-Replication-Get-Changes-All*\",\n \"*DS-Replication-Get-Changes-In-Filtered-Set*\",\n\n /* Identifying GUID used in ACE */\n\n \"*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*\",\n \"*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*\",\n \"*89e95b76-444d-4c62-991a-0facbeda640c*\") \n \n /* The right to perform an operation controlled by an extended access right. */\n\n and winlog.event_data.AccessMask : \"0x100\" and\n not winlog.event_data.SubjectUserName : (\"*$\", \"MSOL_*\")\n", "references": [ "https://threathunterplaybook.com/notebooks/windows/06_credential_access/WIN-180815210510.html", @@ -58,5 +58,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/credential_access_disable_kerberos_preauth.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json index aa19cb45d9352..3d72d63489242 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json @@ -11,10 +11,10 @@ "language": "kuery", "license": "Elastic License v2", "name": "Kerberos Pre-authentication Disabled for User", - "note": "## Triage and analysis\n\n### Investigating Kerberos Pre-authentication Disabled for User\n\nKerberos pre-authentication is an account protection against offline password cracking. When enabled, a user requesting\naccess to a resource initiates communication with the Domain Controller (DC) by sending an Authentication Server Request\n(AS-REQ) message with a timestamp that is encrypted with the hash of their password. If and only if the DC is able to\nsuccessfully decrypt the timestamp with the hash of the user\u2019s password, it will then send an Authentication Server\nResponse (AS-REP) message that contains the Ticket Granting Ticket (TGT) to the user. Part of the AS-REP message is\nsigned with the user\u2019s password. Microsoft's security monitoring [recommendations](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4738) state that `'Don't Require Preauth' \u2013 Enabled` should not be enabled for user accounts because it weakens security for the account\u2019s Kerberos authentication.\n\nAS-REP roasting is an attack against Kerberos for user accounts that do not require pre-authentication, which means that\nif the target user has pre-authentication disabled, an attacker can request authentication data for it and get a TGT that\ncan be brute-forced offline, similarly to Kerberoasting.\n\n#### Possible investigation steps\n\n- Identify the account that performed the action.\n- Check whether this user should be doing this kind of activity.\n- Investigate if the target account is privileged.\n- Contact the account owner and confirm whether they are aware of this activity.\n\n### False positive analysis\n\n- Disabling pre-authentication is a bad security practice and should not be allowed in the domain. The security team\nshould map and monitor any potential benign true positives (B-TPs), especially if the target account is privileged.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the target account's password if there is any risk of TGTs having been retrieved.\n- Reset the password of the origin user if the activity was not recognized by the account owner.\n- Re-enable the preauthentication option for the account.\n\n## Config\n\nThe 'Audit User Account Management' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nAccount Management > \nAudit User Account Management (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Kerberos Pre-authentication Disabled for User\n\nKerberos pre-authentication is an account protection against offline password cracking. When enabled, a user requesting\naccess to a resource initiates communication with the Domain Controller (DC) by sending an Authentication Server Request\n(AS-REQ) message with a timestamp that is encrypted with the hash of their password. If and only if the DC is able to\nsuccessfully decrypt the timestamp with the hash of the user\u2019s password, it will then send an Authentication Server\nResponse (AS-REP) message that contains the Ticket Granting Ticket (TGT) to the user. Part of the AS-REP message is\nsigned with the user\u2019s password. Microsoft's security monitoring [recommendations](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4738) state that `'Don't Require Preauth' \u2013 Enabled` should not be enabled for user accounts because it weakens security for the account\u2019s Kerberos authentication.\n\nAS-REP roasting is an attack against Kerberos for user accounts that do not require pre-authentication, which means that\nif the target user has pre-authentication disabled, an attacker can request authentication data for it and get a TGT that\ncan be brute-forced offline, similarly to Kerberoasting.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Determine if the target account is sensitive or privileged.\n- Inspect the account activities for suspicious or abnormal behaviors in the alert timeframe.\n\n### False positive analysis\n\n- Disabling pre-authentication is a bad security practice and should not be allowed in the domain. The security team\nshould map and monitor any potential benign true positives (B-TPs), especially if the target account is privileged.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the target account's password if there is any risk of TGTs having been retrieved.\n- Re-enable the preauthentication option or disable the target account.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit User Account Management' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nAccount Management > \nAudit User Account Management (Success,Failure)\n```\n", "query": "event.code:4738 and message:\"'Don't Require Preauth' - Enabled\"\n", "references": [ - "https://www.harmj0y.net/blog/activedirectory/roasting-as-reps", + "https://harmj0y.medium.com/roasting-as-reps-e6179a65216b", "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4738", "https://github.com/atc-project/atomic-threat-coverage/blob/master/Atomic_Threat_Coverage/Logging_Policies/LP_0026_windows_audit_user_account_management.md" ], @@ -54,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } 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 5077e1ea51f2e..11e0861ad950b 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 @@ -16,7 +16,7 @@ "query": "file where event.type != \"deletion\" and file.name : (\"ntds_capi_*.pfx\", \"ntds_capi_*.pvk\")\n", "references": [ "https://www.dsinternals.com/en/retrieving-dpapi-backup-keys-from-active-directory/", - "https://www.harmj0y.net/blog/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/" + "https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107" ], "risk_score": 73, "rule_id": "b83a7e96-2eb3-4edf-8346-427b6858d3bd", @@ -59,5 +59,5 @@ ], "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/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 de1cad9c2f081..f57fe0d372a24 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Credential Acquisition via Registry Hive Dumping", - "note": "## Triage and analysis\n\n### Investigating Credential Acquisition via Registry Hive Dumping\n\nDumping registry hives is a common way to access credential information as some hives store credential material.\n\nFor example, the SAM hive stores locally cached credentials (SAM Secrets), and the SECURITY hive stores domain cached\ncredentials (LSA secrets).\n\nDumping these hives in combination with the SYSTEM hive enables the attacker to decrypt these secrets.\n\nThis rule identifies the usage of `reg.exe` to dump SECURITY and/or SAM hives, which potentially indicates the\ncompromise of the credentials stored in the host.\n\n#### Possible investigation steps\n\n- Investigate script execution chain (parent process tree).\n- Confirm whether the involved account should perform this kind of operation.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Investigate if the file was exfiltrated or processed locally by other tools.\n- Scope potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n\n### False positive analysis\n\n- Administrators can export registry hives for backup purposes using command line tools like `reg.exe`. Check whether\nthe user is legitamitely performing this kind of activity.\n\n### Related rules\n\n- Registry Hive File Creation via SMB - a4c7473a-5cb4-4bc1-9d06-e4a75adbc494\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Scope compromised credentials and disable affected accounts.\n- Reset passwords for potentially compromised user and service accounts (Email, services, CRMs, etc.).\n- Reimage the host operating system and restore compromised files to clean versions.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Credential Acquisition via Registry Hive Dumping\n\nDumping registry hives is a common way to access credential information as some hives store credential material.\n\nFor example, the SAM hive stores locally cached credentials (SAM Secrets), and the SECURITY hive stores domain cached\ncredentials (LSA secrets).\n\nDumping these hives in combination with the SYSTEM hive enables the attacker to decrypt these secrets.\n\nThis rule identifies the usage of `reg.exe` to dump SECURITY and/or SAM hives, which potentially indicates the\ncompromise of the credentials stored in the host.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate if the credential material was exfiltrated or processed locally by other tools.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n\n### False positive analysis\n\n- Administrators can export registry hives for backup purposes using command line tools like `reg.exe`. Check whether\nthe user is legitamitely performing this kind of activity.\n\n### Related rules\n\n- Registry Hive File Creation via SMB - a4c7473a-5cb4-4bc1-9d06-e4a75adbc494\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.pe.original_file_name == \"reg.exe\" and\n process.args : (\"save\", \"export\") and\n process.args : (\"hklm\\\\sam\", \"hklm\\\\security\")\n", "references": [ "https://medium.com/threatpunter/detecting-attempts-to-steal-passwords-from-the-registry-7512674487f8" @@ -58,5 +58,5 @@ ], "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_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json index c42b1a068992d..51ae6106124a3 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", - "note": "## Triage and analysis\n\n### Investigating Kerberos Traffic from Unusual Process\n\nKerberos is the default authentication protocol in Active Directory, designed to provide strong authentication for\nclient/server applications by using secret-key cryptography.\n\nDomain-joined hosts usually perform Kerberos traffic using the `lsass.exe` process. This rule detects the occurrence of\ntraffic on the Kerberos port (88) by processes other than `lsass.exe` to detect the unusual request and usage of\nKerberos tickets.\n\n#### Possible investigation steps\n\n- Investigate script execution chain (parent process tree).\n- Investigate other alerts related to the host and user in the last 48 hours.\n- Check if the Destination IP is related to a Domain Controller.\n- Review event ID 4769 for suspicious ticket requests.\n\n### False positive analysis\n\n- This rule uses a Kerberos-related port but does not identify the protocol used on that port. HTTP traffic on a\nnon-standard port or destination IP address unrelated to Domain controllers can create false positives.\n- Exceptions can be added for noisy/frequent connections.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Scope possible compromised credentials based on ticket requests.\n- Isolate the involved host to prevent further post-compromise behavior.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Kerberos Traffic from Unusual Process\n\nKerberos is the default authentication protocol in Active Directory, designed to provide strong authentication for\nclient/server applications by using secret-key cryptography.\n\nDomain-joined hosts usually perform Kerberos traffic using the `lsass.exe` process. This rule detects the occurrence of\ntraffic on the Kerberos port (88) by processes other than `lsass.exe` to detect the unusual request and usage of\nKerberos tickets.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if the Destination IP is related to a Domain Controller.\n- Review event ID 4769 for suspicious ticket requests.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule uses a Kerberos-related port but does not identify the protocol used on that port. HTTP traffic on a\nnon-standard port or destination IP address unrelated to Domain controllers can create false positives.\n- Exceptions can be added for noisy/frequent connections.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n - Ticket requests can be used to investigate potentially compromised accounts.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\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", @@ -46,5 +46,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } 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 db2ddac681a6c..444d6051c1250 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 @@ -56,5 +56,5 @@ "timeline_title": "Comprehensive File Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json index c639af0e1745c..7ac3bf448a672 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "LSASS Memory Dump Handle Access", - "note": "## Triage and analysis\n\n### Investigating LSASS Memory Dump Handle Access\n\nLocal Security Authority Server Service (LSASS) is a process in Microsoft Windows operating systems that is responsible\nfor enforcing security policy on the system. It verifies users logging on to a Windows computer or server, handles\npassword changes, and creates access tokens.\n\nAdversaries may attempt to access credential material stored in LSASS process memory. After a user logs on,the system\ngenerates and stores a variety of credential materials in LSASS process memory. This is meant to facilitate single\nsign-on (SSO) ensuring a user isn\u2019t prompted each time resource access is requested. These credential materials can be\nharvested by an adversary using administrative user or SYSTEM privileges to conduct lateral movement using \n[alternate authentication material](https://attack.mitre.org/techniques/T1550/).\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the correct install path for the process that triggered this detection.\n\n### False positive analysis\n\n- There should be very few if any false positives for this rule. However, it may be tripped by antivirus or endpoint detection and response solutions;\ncheck whether these solutions are installed on the correct paths.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Scope compromised credentials and disable the accounts.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n## Config\n\nEnsure advanced audit policies for Windows are enabled, specifically:\nObject Access policies [Event ID 4656](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656) (Handle to an Object was Requested)\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nSystem Audit Policies >\nObject Access >\nAudit File System (Success,Failure)\nAudit Handle Manipulation (Success,Failure)\n```\n\nAlso, this event generates only if the object\u2019s [SACL](https://docs.microsoft.com/en-us/windows/win32/secauthz/access-control-lists) has the required access control entry (ACE) to handle the use of specific access rights.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating LSASS Memory Dump Handle Access\n\nLocal Security Authority Server Service (LSASS) is a process in Microsoft Windows operating systems that is responsible\nfor enforcing security policy on the system. It verifies users logging on to a Windows computer or server, handles\npassword changes, and creates access tokens.\n\nAdversaries may attempt to access credential material stored in LSASS process memory. After a user logs on,the system\ngenerates and stores a variety of credential materials in LSASS process memory. This is meant to facilitate single\nsign-on (SSO) ensuring a user isn\u2019t prompted each time resource access is requested. These credential materials can be\nharvested by an adversary using administrative user or SYSTEM privileges to conduct lateral movement using \n[alternate authentication material](https://attack.mitre.org/techniques/T1550/).\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There should be very few or no false positives for this rule. If this activity is expected or noisy in your environment,\nconsider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n- If the process is related to antivirus or endpoint detection and response solutions, validate that it is installed on\nthe correct path and signed with the company's valid digital signature.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Scope compromised credentials and disable the accounts.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nEnsure advanced audit policies for Windows are enabled, specifically:\nObject Access policies [Event ID 4656](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656) (Handle to an Object was Requested)\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nSystem Audit Policies >\nObject Access >\nAudit File System (Success,Failure)\nAudit Handle Manipulation (Success,Failure)\n```\n\nAlso, this event generates only if the object\u2019s [SACL](https://docs.microsoft.com/en-us/windows/win32/secauthz/access-control-lists) has the required access control entry (ACE) to handle the use of specific access rights.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "any where event.action == \"File System\" and event.code == \"4656\" and\n\n winlog.event_data.ObjectName : (\n \"?:\\\\Windows\\\\System32\\\\lsass.exe\",\n \"\\\\Device\\\\HarddiskVolume?\\\\Windows\\\\System32\\\\lsass.exe\",\n \"\\\\Device\\\\HarddiskVolume??\\\\Windows\\\\System32\\\\lsass.exe\") and\n\n /* The right to perform an operation controlled by an extended access right. */\n\n (winlog.event_data.AccessMask : (\"0x1fffff\" , \"0x1010\", \"0x120089\", \"0x1F3FFF\") or\n winlog.event_data.AccessMaskDescription : (\"READ_CONTROL\", \"Read from process memory\"))\n\n /* Common Noisy False Positives */\n\n and not winlog.event_data.ProcessName : (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\system32\\\\wbem\\\\WmiPrvSE.exe\",\n \"?:\\\\Windows\\\\System32\\\\dllhost.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\msiexec.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\*.exe\",\n \"?:\\\\Windows\\\\explorer.exe\")\n", "references": [ "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656", @@ -56,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/credential_access_microsoft_365_brute_force_user_account_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json index 823d0d29b301f..26ba1d845ed35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json @@ -17,7 +17,7 @@ "license": "Elastic License v2", "name": "Attempts to Brute Force a Microsoft 365 User Account", "note": "## Config\n\nThe Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "query": "event.dataset:o365.audit and event.provider:(AzureActiveDirectory or Exchange) and\n event.category:authentication and event.action:(UserLoginFailed or PasswordLogonInitialAuthUsingPassword) and\n not o365.audit.LogonError:(UserAccountNotFound or EntitlementGrantsNotFound or UserStrongAuthEnrollmentRequired or\n UserStrongAuthClientAuthNRequired or InvalidReplyTo) and event.outcome:success\n", + "query": "event.dataset:o365.audit and event.provider:(AzureActiveDirectory or Exchange) and\n event.category:authentication and event.action:(UserLoginFailed or PasswordLogonInitialAuthUsingPassword) and\n not o365.audit.LogonError:(UserAccountNotFound or EntitlementGrantsNotFound or UserStrongAuthEnrollmentRequired or\n UserStrongAuthClientAuthNRequired or InvalidReplyTo)\n", "references": [ "https://blueteamblog.com/7-ways-to-monitor-your-office-365-logs-using-siem" ], @@ -56,5 +56,5 @@ "value": 10 }, "type": "threshold", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json index ba4500b56c6df..06f77fe83ba14 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json @@ -15,7 +15,7 @@ "license": "Elastic License v2", "name": "Potential Password Spraying of Microsoft 365 User Accounts", "note": "## Config\n\nThe Office 365 Logs 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 or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\") and event.outcome:success\n", + "query": "event.dataset:o365.audit and event.provider:(Exchange or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\")\n", "risk_score": 73, "rule_id": "3efee4f0-182a-40a8-a835-102c68a4175d", "severity": "high", @@ -51,5 +51,5 @@ "value": 25 }, "type": "threshold", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json index 05bcd8d38ac37..e9f699e7a62a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Mimikatz Memssp Log File Detected", - "note": "## Triage and analysis.\n\n### Investigating Mimikatz Memssp Log File Detected\n\n[Mimikatz](https://github.com/gentilkiwi/mimikatz) is an open-source tool used to collect, decrypt, and/or use cached\ncredentials. This tool is commonly abused by adversaries during the post-compromise stage where adversaries have gained\nan initial foothold on an endpoint and are looking to elevate privileges and seek out additional authentication objects\nsuch as tokens/hashes/credentials that can then be used to laterally move and pivot across a network.\n\nThis rule looks for the creation of a file named `mimilsa.log`, which is generated when using the Mimikatz misc::memssp\nmodule, which injects a malicious Windows SSP to collect locally authenticated credentials, which includes the computer\naccount password, running service credentials, and any accounts that logon.\n\n#### Possible investigation steps\n\n- Investigate script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Scope potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n- Retrieve and inspect the log file contents.\n- By default, the log file is created in the same location as the DLL file.\n- Search for DLL files created in the location, and retrieve any DLLs that are not signed:\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of these files.\n - Search for the existence of these files in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This file name `mimilsa.log` should not legitimately be created.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the host is a Domain Controller (DC):\n - Activate your incident response plan for total Active Directory compromise.\n - Review the permissions of users that can access the DCs.\n- Reset passwords for all compromised accounts.\n- Disable remote login for compromised user accounts.\n- Reboot the host to remove the injected SSP from memory.\n- Reimage the host operating system or restore compromised files to clean versions.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Mimikatz Memssp Log File Detected\n\n[Mimikatz](https://github.com/gentilkiwi/mimikatz) is an open-source tool used to collect, decrypt, and/or use cached\ncredentials. This tool is commonly abused by adversaries during the post-compromise stage where adversaries have gained\nan initial foothold on an endpoint and are looking to elevate privileges and seek out additional authentication objects\nsuch as tokens/hashes/credentials that can then be used to laterally move and pivot across a network.\n\nThis rule looks for the creation of a file named `mimilsa.log`, which is generated when using the Mimikatz misc::memssp\nmodule, which injects a malicious Windows SSP to collect locally authenticated credentials, which includes the computer\naccount password, running service credentials, and any accounts that logon.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n- Retrieve and inspect the log file contents.\n- Search for DLL files created in the same location as the log file, and retrieve unsigned DLLs.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of these files.\n - Search for the existence of these files in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Identify the process that created the DLL using file creation events.\n\n### False positive analysis\n\n- This file name `mimilsa.log` should not legitimately be created.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the host is a Domain Controller (DC):\n - Activate your incident response plan for total Active Directory compromise.\n - Review the privileges assigned to users that can access the DCs to ensure that the least privilege principle is\n being followed and reduce the attack surface.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reboot the host to remove the injected SSP from memory.\n- Reimage the host operating system or restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "file where file.name : \"mimilsa.log\" and process.name : \"lsass.exe\"\n", "risk_score": 73, "rule_id": "ebb200e8-adf0-43f8-a0bb-4ee5b5d852c6", @@ -43,5 +43,5 @@ ], "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_mimikatz_powershell_module.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json new file mode 100644 index 0000000000000..0942b6fe8da8c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json @@ -0,0 +1,57 @@ +{ + "author": [ + "Elastic" + ], + "description": "Mimikatz is a credential dumper capable of obtaining plaintext Windows account logins and passwords, along with many other features that make it useful for testing the security of networks. This rule detects Invoke-Mimikatz PowerShell script and alike.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Potential Invoke-Mimikatz PowerShell Script", + "note": "## Triage and analysis\n\n### Investigating Mimikatz PowerShell Activity\n\n[Mimikatz](https://github.com/gentilkiwi/mimikatz) is an open-source tool used to collect, decrypt, and/or use cached\ncredentials. This tool is commonly abused by adversaries during the post-compromise stage where adversaries have gained\nan initial foothold on an endpoint and are looking to elevate privileges and seek out additional authentication objects\nsuch as tokens/hashes/credentials that can then be used to move laterally and pivot across a network.\n\nThis rule looks for PowerShell scripts that load mimikatz in memory, like Invoke-Mimikataz, which are used to dump\ncredentials from the Local Security Authority Subsystem Service (LSASS). Any activity triggered from this rule should be\ntreated with high priority as it typically represents an active adversary.\n\nMore information about Mimikatz components and how to detect/prevent them can be found on [ADSecurity](https://adsecurity.org/?page_id=1821).\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n - Invoke-Mimitakz and alike scripts heavily use other capabilities covered by other detections described in the\n \"Related Rules\" section.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the\ntarget host.\n - Examine network and security events in the environment to identify potential lateral movement using compromised credentials.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Mimikatz Memssp Log File Detected - ebb200e8-adf0-43f8-a0bb-4ee5b5d852c6\n- Modification of WDigest Security Provider - d703a5af-d5b0-43bd-8ddb-7a5d500b7da5\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Validate that cleartext passwords are disabled in memory for use with `WDigest`.\n- Look into preventing access to `LSASS` using capabilities such as LSA protection or antivirus/EDR tools that provide\nthis capability.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "query": "event.category:process and\npowershell.file.script_block_text:(\n (DumpCreds and\n DumpCerts) or\n \"sekurlsa::logonpasswords\" or\n (\"crypto::certificates\" and\n \"CERT_SYSTEM_STORE_LOCAL_MACHINE\")\n)\n", + "references": [ + "https://attack.mitre.org/software/S0002/", + "https://raw.githubusercontent.com/EmpireProject/Empire/master/data/module_source/credentials/Invoke-Mimikatz.ps1" + ], + "risk_score": 73, + "rule_id": "ac96ceb8-4399-4191-af1d-4feeac1f1f46", + "severity": "high", + "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": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json index 0c434f8b78116..6e0dd264229d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of WDigest Security Provider", - "note": "## Triage and analysis.\n\n### Investigating Modification of WDigest Security Provider\n\nIn Windows XP, Microsoft added support for a protocol known as WDigest. The WDigest protocol allows clients to send\ncleartext credentials to Hypertext Transfer Protocol (HTTP) and Simple Authentication Security Layer (SASL) applications\nbased on RFC 2617 and 2831. Windows versions up to 8 and 2012 store logon credentials in memory in plaintext by default,\nwhich is no longer the case with newer Windows versions.\n\nStill, attackers can force WDigest to store the passwords insecurely on the memory by modifying the\n`HKLM\\SYSTEM\\*ControlSet*\\Control\\SecurityProviders\\WDigest\\UseLogonCredential` registry key. This activity is\ncommonly related to the execution of credential dumping tools.\n\n#### Possible investigation steps\n\n- It is unlikely that the monitored registry key was modified legitimately in newer versions of Windows. Analysts should\ntreat any activity triggered from this rule with high priority as it typically represents an active adversary.\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Determine if credential dumping tools were run on the host and if any suspicious tool is found:\n - Retrieve the file.\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for other compromised hosts.\n- Scope potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the entire domain to credential compromises and\nconsequently unauthorized access.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Disable user account\u2019s ability to log in remotely.\n- Reset the password for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Reimage the host operating system and restore compromised files to clean versions.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Modification of WDigest Security Provider\n\nIn Windows XP, Microsoft added support for a protocol known as WDigest. The WDigest protocol allows clients to send\ncleartext credentials to Hypertext Transfer Protocol (HTTP) and Simple Authentication Security Layer (SASL) applications\nbased on RFC 2617 and 2831. Windows versions up to 8 and 2012 store logon credentials in memory in plaintext by default,\nwhich is no longer the case with newer Windows versions.\n\nStill, attackers can force WDigest to store the passwords insecurely on the memory by modifying the\n`HKLM\\SYSTEM\\*ControlSet*\\Control\\SecurityProviders\\WDigest\\UseLogonCredential` registry key. This activity is\ncommonly related to the execution of credential dumping tools.\n\n#### Possible investigation steps\n\n- It is unlikely that the monitored registry key was modified legitimately in newer versions of Windows. Analysts should\ntreat any activity triggered from this rule with high priority as it typically represents an active adversary.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if credential dumping tools were run on the host, and retrieve and analyze suspicious executables:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the entire domain to credential compromises and\nconsequently unauthorized access.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type : (\"creation\", \"change\") and\n registry.path : \n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\"\n and registry.data.strings : (\"1\", \"0x00000001\")\n", "references": [ "https://www.csoonline.com/article/3438824/how-to-detect-and-halt-credential-theft-via-windows-wdigest.html", @@ -55,5 +55,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_moving_registry_hive_via_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json index 3c6d4d2c1a166..f75b18ce71268 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies the creation or modification of a medium-size registry hive file on an SMB share, which may indicate an exfiltration attempt of a previously dumped SAM registry hive for credential extraction on an attacker-controlled system.", + "description": "Identifies the creation or modification of a medium-size registry hive file on a Server Message Block (SMB) share, which may indicate an exfiltration attempt of a previously dumped Security Account Manager (SAM) registry hive for credential extraction on an attacker-controlled system.", "from": "now-9m", "index": [ "logs-endpoint.events.*" @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Registry File Creation in SMB Share", - "note": "## Triage and analysis\n\n### Investigating Windows Registry File Creation in SMB Share\n\nDumping registry hives is a common way to access credential information. Some hives store credential material, as is the\ncase for the SAM hive, which stores locally cached credentials (SAM Secrets), and the SECURITY hive, which stores domain\ncached credentials (LSA secrets). Dumping these hives in combination with the SYSTEM hive enables the attacker to\ndecrypt these secrets.\n\nAttackers can try to evade detection on the host by transferring this data to a system that is not\nmonitored to be parsed and decrypted. This rule identifies the creation or modification of a medium-size registry hive\nfile on an SMB share, which may indicate this kind of exfiltration attempt.\n\n#### Possible investigation steps\n\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Confirm whether the account owner is aware of the operation.\n- Examine command line logs for the period when the alert was triggered.\n- Capture the registry file(s) to scope the compromised credentials in an eventual Incident Response.\n\n### False positive analysis\n\n- Administrators can export registry hives for backup purposes. Check whether the user should be performing this kind of\nactivity and is aware of it.\n\n### Related rules\n\n- Credential Acquisition via Registry Hive Dumping - a7e7bfa3-088e-4f13-b29e-3986e0e756b8\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Scope compromised credentials and disable associated accounts.\n- Reset passwords for compromised accounts.\n- Reimage the host operating system and restore compromised files to clean versions.\n", + "note": "## Triage and analysis\n\n### Investigating Windows Registry File Creation in SMB Share\n\nDumping registry hives is a common way to access credential information. Some hives store credential material, as is the\ncase for the SAM hive, which stores locally cached credentials (SAM secrets), and the SECURITY hive, which stores domain\ncached credentials (LSA secrets). Dumping these hives in combination with the SYSTEM hive enables the attacker to\ndecrypt these secrets.\n\nAttackers can try to evade detection on the host by transferring this data to a system that is not\nmonitored to be parsed and decrypted. This rule identifies the creation or modification of a medium-size registry hive\nfile on an SMB share, which may indicate this kind of exfiltration attempt.\n\n#### Possible investigation steps\n\n- Investigate other alerts associated with the user/source host during the past 48 hours.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Inspect the source host for suspicious or abnormal behaviors in the alert timeframe.\n- Capture the registry file(s) to determine the extent of the credential compromise in an eventual incident response.\n\n### False positive analysis\n\n- Administrators can export registry hives for backup purposes. Check whether the user should be performing this kind of\nactivity and is aware of it.\n\n### Related rules\n\n- Credential Acquisition via Registry Hive Dumping - a7e7bfa3-088e-4f13-b29e-3986e0e756b8\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "file where event.type == \"creation\" and\n /* regf file header */\n file.Ext.header_bytes : \"72656766*\" and file.size >= 30000 and\n process.pid == 4 and user.id : \"s-1-5-21*\"\n", "risk_score": 47, "rule_id": "a4c7473a-5cb4-4bc1-9d06-e4a75adbc494", @@ -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/credential_access_posh_minidump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json index 276ee92e3b086..3330846bfc6c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell MiniDump Script", - "note": "## Triage and analysis.\n\n### Investigating PowerShell MiniDump Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse Process Memory Dump capabilities to extract credentials from LSASS or to obtain other\nprivileged information stored in the process memory.\n\n#### Possible investigation steps\n\n- Examine script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Check if the imported function was executed and which process it targeted.\n\n### False positive analysis\n\n- Regular users do not have a business justification for using scripting utilities to dump process memory, making false\npositives unlikely.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n- Reset the password for the user account.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell MiniDump Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse Process Memory Dump capabilities to extract credentials from LSASS or to obtain other\nprivileged information stored in the process memory.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Check if the imported function was executed and which process it targeted.\n\n### False positive analysis\n\n- Regular users do not have a business justification for using scripting utilities to dump process memory, making false\npositives unlikely.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and powershell.file.script_block_text:(MiniDumpWriteDump or MiniDumpWithFullMemory or pmuDetirWpmuDiniM)\n", "references": [ "https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Out-Minidump.ps1", @@ -79,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json index 2719736e43ddc..e0171ca436aef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Kerberos Ticket Request", - "note": "## Triage and analysis\n\n### Investigating Explicit PowerShell Kerberos Ticket Request\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, making\nit available for use in various environments, creating an attractive way for attackers to execute code.\n\nAccounts associated with a service principal name (SPN) are viable targets for Kerberoasting attacks, which use brute\nforce to crack the user password, which is used to encrypt a Kerberos TGS ticket.\n\nAttackers can use PowerShell to request these Kerberos tickets, with the intent of extracting them from memory to\nperform Kerberoasting.\n\n#### Possible investigation steps\n\n- Retrieve the script contents.\n- Investigate the script execution chain (parent process tree).\n- Investigate if the script was executed, and if so, which account was targeted.\n- Check whether this user should be doing this kind of activity.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Check if the script has any other functionality that can be potentially malicious.\n- Investigate other alerts related to the host and user in the last 48 hours.\n- Review event ID [4769](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4769)\nrelated to this account and service name for additional information.\n\n### False positive analysis\n\n- A possible false positive can be identified if the script content is not malicious/harmful or does not request\nKerberos tickets for user accounts, as computer accounts are not vulnerable to Kerberoasting due to complex password\nrequirements and policy.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the password of the involved accounts. Priority should be given to privileged accounts.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n", + "note": "## Triage and analysis\n\n### Investigating Explicit PowerShell Kerberos Ticket Request\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, making\nit available for use in various environments, creating an attractive way for attackers to execute code.\n\nAccounts associated with a service principal name (SPN) are viable targets for Kerberoasting attacks, which use brute\nforce to crack the user password, which is used to encrypt a Kerberos TGS ticket.\n\nAttackers can use PowerShell to request these Kerberos tickets, with the intent of extracting them from memory to\nperform Kerberoasting.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate if the script was executed, and if so, which account was targeted.\n- Validate if the account has an SPN associated with it.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Check if the script has any other functionality that can be potentially malicious.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Review event ID [4769](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4769)\nrelated to this account and service name for additional information.\n\n### False positive analysis\n\n- A possible false positive can be identified if the script content is not malicious/harmful or does not request\nKerberos tickets for user accounts, as computer accounts are not vulnerable to Kerberoasting due to complex password\nrequirements and policy.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services. Prioritize privileged accounts.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n KerberosRequestorSecurityToken\n )\n", "references": [ "https://cobalt.io/blog/kerberoast-attack-techniques", @@ -80,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json new file mode 100644 index 0000000000000..47a585adb9af3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempt to coerce a local NTLM authentication via HTTP using the Windows Printer Spooler service as a target. An adversary may use this primitive in combination with other techniques to elevate privileges on a compromised system.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Local NTLM Relay via HTTP", + "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : \"rundll32.exe\" and\n \n /* Rundll32 WbeDav Client */\n process.args : (\"?:\\\\Windows\\\\System32\\\\davclnt.dll,DavSetCookie\", \"?:\\\\Windows\\\\SysWOW64\\\\davclnt.dll,DavSetCookie\") and \n \n /* Access to named pipe via http */\n process.args : (\"http*/print/pipe/*\", \"http*/pipe/spoolss\", \"http*/pipe/srvsvc\")\n", + "references": [ + "https://github.com/med0x2e/NTLMRelay2Self", + "https://github.com/topotam/PetitPotam", + "https://github.com/dirkjanm/krbrelayx/blob/master/printerbug.py" + ], + "risk_score": 73, + "rule_id": "4682fd2c-cfae-47ed-a543-9bed37657aa6", + "severity": "high", + "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": "T1212", + "name": "Exploitation for Credential Access", + "reference": "https://attack.mitre.org/techniques/T1212/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json index 110a71702fb81..f7876ff80e688 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies remote access to the registry to potentially dump credential data from the SAM registry hive in preparation for credential access and privileges elevation.", + "description": "Identifies remote access to the registry to potentially dump credential data from the Security Account Manager (SAM) registry hive in preparation for credential access and privileges elevation.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Remote Credential Access via Registry", - "note": "## Triage and analysis\n\n### Investigating Potential Remote Credential Access via Registry\n\nDumping registry hives is a common way to access credential information. Some hives store credential material, \nsuch as the SAM hive, which stores locally cached credentials (SAM Secrets), and the SECURITY hive, which stores domain\ncached credentials (LSA secrets). Dumping these hives in combination with the SYSTEM hive enables the attacker to\ndecrypt these secrets.\n\nAttackers can use tools like secretsdump.py or CrackMapExec to dump the registry hives remotely, and use dumped\ncredentials to access other systems in the domain.\n\n#### Possible investigation steps\n\n- Identify the target host role, involved account, and source host.\n- Determine the privileges assigned to any compromised accounts.\n- Investigate other alerts related to the involved user and source host in the last 48 hours.\n- Scope potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n\n### False positive analysis\n\n- False positives for this rule are unlikely. Any activity that triggered the alert and is not inherently malicious must\nbe monitored by the security team.\n\n### Related rules\n\n- Credential Acquisition via Registry Hive Dumping - a7e7bfa3-088e-4f13-b29e-3986e0e756b8\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Scope compromised credentials and disable the accounts.\n- Reset the passwords of compromised accounts.\n- Determine if other hosts were compromised.\n\n## Config\n\nThis rule uses Elastic Endpoint file creation and System Integration events for correlation. Both data should be\ncollected from the host for this detection to work.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Potential Remote Credential Access via Registry\n\nDumping registry hives is a common way to access credential information. Some hives store credential material, \nsuch as the SAM hive, which stores locally cached credentials (SAM secrets), and the SECURITY hive, which stores domain\ncached credentials (LSA secrets). Dumping these hives in combination with the SYSTEM hive enables the attacker to\ndecrypt these secrets.\n\nAttackers can use tools like secretsdump.py or CrackMapExec to dump the registry hives remotely, and use dumped\ncredentials to access other systems in the domain.\n\n#### Possible investigation steps\n\n- Identify the specifics of the involved assets, such as their role, criticality, and associated users.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Determine the privileges of the compromised accounts.\n- Investigate other alerts associated with the user/source host during the past 48 hours.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently malicious\nmust be monitored by the security team.\n\n### Related rules\n\n- Credential Acquisition via Registry Hive Dumping - a7e7bfa3-088e-4f13-b29e-3986e0e756b8\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine if other hosts were compromised.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Reimage the host operating system or restore the compromised files to clean versions.\n- Ensure that the machine has the latest security updates and is not running unsupported Windows versions.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThis rule uses Elastic Endpoint file creation and system integration events for correlation. Both data should be\ncollected from the host for this detection to work.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "sequence by host.id, user.id with maxspan=1m\n [authentication where\n event.outcome == \"success\" and\n winlog.logon.type == \"Network\" and not user.name == \"ANONYMOUS LOGON\" and\n not user.domain == \"NT AUTHORITY\" and source.ip != \"127.0.0.1\" and source.ip !=\"::1\"]\n [file where event.action == \"creation\" and process.name : \"svchost.exe\" and\n file.Ext.header_bytes : \"72656766*\" and user.id : \"S-1-5-21-*\" and file.size >= 30000]\n", "references": [ "https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py" @@ -68,5 +68,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } 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 a91f9316d8e87..d7b15ae14924e 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 @@ -16,7 +16,7 @@ "query": "process where event.type in (\"start\", \"process_started\") and\n (process.pe.original_file_name:\"vaultcmd.exe\" or process.name:\"vaultcmd.exe\") and\n process.args:\"/list*\"\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16", - "https://rastamouse.me/blog/rdp-jump-boxes/" + "https://web.archive.org/web/20201004080456/https://rastamouse.me/blog/rdp-jump-boxes/" ], "risk_score": 47, "rule_id": "be8afaed-4bcd-4e0a-b5f9-5562003dde81", @@ -59,5 +59,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_seenabledelegationprivilege_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json index 14a042b2cff52..baa66851cb366 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Sensitive Privilege SeEnableDelegationPrivilege assigned to a User", - "note": "## Triage and analysis\n\n### Investigating Sensitive Privilege SeEnableDelegationPrivilege assigned to a User\n\nKerberos delegation is an Active Directory feature that allows user and computer accounts to impersonate other accounts,\nact on their behalf, and use their privileges. Delegation (constrained and unconstrained) can be configured\nfor user and computer objects.\n\nEnabling unconstrained delegation for a computer causes the computer to store the ticket-granting ticket\n(TGT) in memory at any time an account connects to the computer, so it can be used by the computer for impersonation\nwhen needed. Risk is heightened if an attacker compromises computers with unconstrained delegation enabled, as they\ncould extract TGTs from memory and then replay them to move laterally on the domain. If the attacker coerces a privileged\nuser to connect to the server, or if the user does so routinely, the account will be compromised and the attacker will\nbe able to pass-the-ticket to privileged assets.\n\nSeEnableDelegationPrivilege is a user right that is controlled within the Local Security Policy of a domain controller\nand is managed through Group Policy. This setting is named **Enable computer and user accounts to be trusted for\ndelegation**.\n\nIt is critical to control the assignment of this privilege. A user with this privilege and write access to a computer\ncan control delegation settings, perform the attacks described above, and harvest TGTs from any user that connects to\nthe system. \n\n#### Possible investigation steps\n\n- Investigate how the privilege was assigned to the user and who assigned it.\n- Investigate other potentially malicious activity that was performed by the user that assigned the privileges using the\n`user.id` and `winlog.activity_id` fields as a filter during the past 48 hours.\n- Investigate other alerts associated with the involved accounts during the past 48 hours.\n\n### False positive analysis\n\n- The SeEnableDelegationPrivilege privilege should not be assigned to users. If this rule is triggered in your\nenvironment legitimately, the security team should notify the administrators about the risks of using it.\n\n### Related rules\n\n- KRBTGT Delegation Backdoor - e052c845-48d0-4f46-8a13-7d0aba05df82\n\n### Response and remediation\n\n- Immediate response should be taken to validate, investigate, and potentially contain the activity to prevent further\npost-compromise behavior.\n\n## Config\n\nThe 'Audit Authorization Policy Change' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policy Configuration >\nAudit Policies >\nPolicy Change >\nAudit Authorization Policy Change (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Sensitive Privilege SeEnableDelegationPrivilege assigned to a User\n\nKerberos delegation is an Active Directory feature that allows user and computer accounts to impersonate other accounts,\nact on their behalf, and use their privileges. Delegation (constrained and unconstrained) can be configured\nfor user and computer objects.\n\nEnabling unconstrained delegation for a computer causes the computer to store the ticket-granting ticket\n(TGT) in memory at any time an account connects to the computer, so it can be used by the computer for impersonation\nwhen needed. Risk is heightened if an attacker compromises computers with unconstrained delegation enabled, as they\ncould extract TGTs from memory and then replay them to move laterally on the domain. If the attacker coerces a privileged\nuser to connect to the server, or if the user does so routinely, the account will be compromised and the attacker will\nbe able to pass-the-ticket to privileged assets.\n\nSeEnableDelegationPrivilege is a user right that is controlled within the Local Security Policy of a domain controller\nand is managed through Group Policy. This setting is named **Enable computer and user accounts to be trusted for\ndelegation**.\n\nIt is critical to control the assignment of this privilege. A user with this privilege and write access to a computer\ncan control delegation settings, perform the attacks described above, and harvest TGTs from any user that connects to\nthe system. \n\n#### Possible investigation steps\n\n- Investigate how the privilege was assigned to the user and who assigned it.\n- Investigate other potentially malicious activity that was performed by the user that assigned the privileges using the\n`user.id` and `winlog.activity_id` fields as a filter during the past 48 hours.\n- Investigate other alerts associated with the users/host during the past 48 hours.\n\n### False positive analysis\n\n- The SeEnableDelegationPrivilege privilege should not be assigned to users. If this rule is triggered in your\nenvironment legitimately, the security team should notify the administrators about the risks of using it.\n\n### Related rules\n\n- KRBTGT Delegation Backdoor - e052c845-48d0-4f46-8a13-7d0aba05df82\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Remove the privilege from the account.\n- Review the privileges of the administrator account that performed the action.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Authorization Policy Change' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policy Configuration >\nAudit Policies >\nPolicy Change >\nAudit Authorization Policy Change (Success,Failure)\n```\n", "query": "event.action: \"Authorization Policy Change\" and event.code:4704 and winlog.event_data.PrivilegeList:\"SeEnableDelegationPrivilege\"\n", "references": [ "https://blog.harmj0y.net/activedirectory/the-most-dangerous-user-right-you-probably-have-never-heard-of/", @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json index 71c47ee2a7bc3..e7f5161885910 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Detects when a user account has the servicePrincipalName attribute modified. Attackers can abuse write privileges over a user to configure SPNs so that they can perform Kerberoasting. Administrators can also configure this for legitimate purposes, exposing the account to Kerberoasting.", + "description": "Detects when a user account has the servicePrincipalName attribute modified. Attackers can abuse write privileges over a user to configure Service Principle Names (SPNs) so that they can perform Kerberoasting. Administrators can also configure this for legitimate purposes, exposing the account to Kerberoasting.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "User account exposed to Kerberoasting", - "note": "## Triage and analysis\n\n### Investigating User account exposed to Kerberoasting\n\nService Principal Names (SPNs) are names by which Kerberos clients uniquely identify service instances for Kerberos target\ncomputers.\n\nBy default, only computer accounts have SPNs, which creates no significant risk, since machine accounts have a default\ndomain policy that rotates their passwords every 30 days, and the password is composed of 120 random characters, making\nthem invulnerable to Kerberoasting.\n\nA user account with an SPN assigned is considered a Service Account, and is accessible to the entire domain. If any\nuser in the directory requests a ticket-granting service (TGS), the domain controller will encrypt it with the secret\nkey of the account executing the service. An attacker can potentially perform a Kerberoasting attack with this\ninformation, as the human-defined password is likely to be less complex.\n\nFor scenarios where SPNs cannot be avoided on user accounts, Microsoft provides the Group Managed Service Accounts (gMSA)\nfeature, which ensures that account passwords are robust and changed regularly and automatically. More information can\nbe found [here](https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview).\n\nAttackers can also perform \"Targeted Kerberoasting\", which consists of adding fake SPNs to user accounts that they have\nwrite privileges to, making them potentially vulnerable to Kerberoasting.\n\n#### Possible investigation steps\n\n- Identify the account that performed the action.\n- Check whether this user should be doing this kind of activity.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate if the target account is a member of privileged groups (Domain Admins, Enterprise Admins, etc.).\n- Investigate if tickets have been requested for the target account.\n- Investigate other alerts related to the user in the last 48 hours.\n\n### False positive analysis\n\n- The use of user accounts as service accounts is a bad security practice and should not be allowed in the domain. The\nsecurity team should map and monitor any potential benign true positive (B-TP), especially if the account is privileged.\nDomain Administrators that define this kind of setting can put the domain at risk as user accounts don't have the same\nsecurity standards as computer accounts (which have long, complex, random passwords that change frequently), exposing\nthem to credential cracking attacks (Kerberoasting, brute force, etc.).\n\n### Response and remediation \n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the passwords of affected accounts, prioritizing privileged accounts.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n\n## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nDS Access >\nAudit Directory Service Changes (Success,Failure)\n```\n\nThe above policy does not cover User objects, so we need to set up an AuditRule using https://github.com/OTRF/Set-AuditRule.\nAs this specifies the servicePrincipalName Attribute GUID, it is expected to be low noise.\n\n```\nSet-AuditRule -AdObjectPath 'AD:\\CN=Users,DC=Domain,DC=com' -WellKnownSidType WorldSid -Rights WriteProperty -InheritanceFlags Children -AttributeGUID f3a64788-5306-11d1-a9c5-0000f80367c1 -AuditFlags Success\n```\n", + "note": "## Triage and analysis\n\n### Investigating User account exposed to Kerberoasting\n\nService Principal Names (SPNs) are names by which Kerberos clients uniquely identify service instances for Kerberos target\ncomputers.\n\nBy default, only computer accounts have SPNs, which creates no significant risk, since machine accounts have a default\ndomain policy that rotates their passwords every 30 days, and the password is composed of 120 random characters, making\nthem invulnerable to Kerberoasting.\n\nA user account with an SPN assigned is considered a service account, and is accessible to the entire domain. If any\nuser in the directory requests a ticket-granting service (TGS), the domain controller will encrypt it with the secret\nkey of the account executing the service. An attacker can potentially perform a Kerberoasting attack with this\ninformation, as the human-defined password is likely to be less complex.\n\nFor scenarios where SPNs cannot be avoided on user accounts, Microsoft provides the Group Managed Service Accounts (gMSA)\nfeature, which ensures that account passwords are robust and changed regularly and automatically. More information can\nbe found [here](https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview).\n\nAttackers can also perform \"Targeted Kerberoasting\", which consists of adding fake SPNs to user accounts that they have\nwrite privileges to, making them potentially vulnerable to Kerberoasting.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate if the target account is a member of privileged groups (Domain Admins, Enterprise Admins, etc.).\n- Investigate if tickets have been requested for the target account.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- The use of user accounts as service accounts is a bad security practice and should not be allowed in the domain. The\nsecurity team should map and monitor any potential benign true positive (B-TP), especially if the account is privileged.\nDomain Administrators that define this kind of setting can put the domain at risk as user accounts don't have the same\nsecurity standards as computer accounts (which have long, complex, random passwords that change frequently), exposing\nthem to credential cracking attacks (Kerberoasting, brute force, etc.).\n\n### Response and remediation \n\n- Initiate the incident response process based on the outcome of the triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services. Prioritize privileged accounts.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success, Failure).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration >\nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nDS Access >\nAudit Directory Service Changes (Success,Failure)\n```\n\nThe above policy does not cover User objects, so set up an AuditRule using https://github.com/OTRF/Set-AuditRule.\nAs this specifies the servicePrincipalName Attribute GUID, it is expected to be low noise.\n\n```\nSet-AuditRule -AdObjectPath 'AD:\\CN=Users,DC=Domain,DC=com' -WellKnownSidType WorldSid -Rights WriteProperty -InheritanceFlags Children -AttributeGUID f3a64788-5306-11d1-a9c5-0000f80367c1 -AuditFlags Success\n```\n", "query": "event.action:\"Directory Service Changes\" and event.code:5136 and winlog.event_data.ObjectClass:\"user\" \nand winlog.event_data.AttributeLDAPDisplayName:\"servicePrincipalName\"\n", "references": [ "https://www.thehacker.recipes/ad/movement/access-controls/targeted-kerberoasting", @@ -58,5 +58,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/credential_access_suspicious_winreg_access_via_sebackup_priv.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json index 8a6219c80839f..f2b832d09a9cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies remote access to the registry via an account with Backup Operators group membership. This may indicate an attempt to exfiltrate credentials via dumping the SAM registry hive in preparation for credential access and privileges elevation.", + "description": "Identifies remote access to the registry using an account with Backup Operators group membership. This may indicate an attempt to exfiltrate credentials by dumping the Security Account Manager (SAM) registry hive in preparation for credential access and privileges elevation.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -68,5 +68,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json index 32cfc08f94770..7bab4403c4114 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json @@ -16,7 +16,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Symbolic Link to Shadow Copy Created", - "note": "## Triage and analysis\n\n### Investigating Symbolic Link to Shadow Copy Created\n\nShadow copies are backups or snapshots of an endpoint's files or volumes while they are in use. Adversaries may attempt\nto discover and create symbolic links to these shadow copies in order to copy sensitive information offline. If Active\nDirectory (AD) is in use, often the ntds.dit file is a target as it contains password hashes, but an offline copy is\nneeded to extract these hashes and potentially conduct lateral movement.\n\n#### Possible investigation steps\n\n- Determine if a volume shadow copy was recently created on this endpoint.\n- Review priviledges of the end user as this requires administrative access.\n- Verify if the ntds.dit file was successfully copied and determine its copy destination.\n- Investigate for registry SYSTEM file copies made recently or saved via Reg.exe.\n- Investigate recent deletions of volume shadow copies.\n- Identify other files potentially copied from volume shadow copy paths directly.\n\n### False positive analysis\n\n- This rule should cause very few false positives. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules \n\n- NTDS or SAM Database File Copied - 3bc6deaa-fbd4-433a-ae21-3e892f95624f\n\n### Response and remediation \n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- If the entire domain or the `krbtgt` user was compromised:\n - Activate your incident response plan for total Active Directory compromise which should include, but not be limited\n to, a password reset (twice) of the `krbtgt` user.\n- Locate and remove static files copied from volume shadow copies.\n- Command-Line tool mklink should require administrative access by default unless in developer mode.\n\n## Config\n\nEnsure advanced audit policies for Windows are enabled, specifically:\nObject Access policies [Event ID 4656](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656) (Handle to an Object was Requested) \n \n``` \nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nSystem Audit Policies > \nObject Access > \nAudit File System (Success,Failure) \nAudit Handle Manipulation (Success,Failure) \n``` \n \nThis event will only trigger if symbolic links are created from a new process spawning cmd.exe or powershell.exe with the correct arguments. \nDirect access to a shell and calling symbolic link creation tools will not generate an event matching this rule. \n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Symbolic Link to Shadow Copy Created\n\nShadow copies are backups or snapshots of an endpoint's files or volumes while they are in use. Adversaries may attempt\nto discover and create symbolic links to these shadow copies in order to copy sensitive information offline. If Active\nDirectory (AD) is in use, often the ntds.dit file is a target as it contains password hashes, but an offline copy is\nneeded to extract these hashes and potentially conduct lateral movement.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if a volume shadow copy was recently created on this endpoint.\n- Review privileges of the end user as this requires administrative access.\n- Verify if the ntds.dit file was successfully copied and determine its copy destination.\n- Investigate for registry SYSTEM file copies made recently or saved via Reg.exe.\n- Investigate recent deletions of volume shadow copies.\n- Identify other files potentially copied from volume shadow copy paths directly.\n\n### False positive analysis\n\n- This rule should cause very few false positives. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules \n\n- NTDS or SAM Database File Copied - 3bc6deaa-fbd4-433a-ae21-3e892f95624f\n\n### Response and remediation \n\n- Initiate the incident response process based on the outcome of the triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the entire domain or the `krbtgt` user was compromised:\n - Activate your incident response plan for total Active Directory compromise which should include, but not be limited\n to, a password reset (twice) of the `krbtgt` user.\n- Locate and remove static files copied from volume shadow copies.\n- Command-Line tool mklink should require administrative access by default unless in developer mode.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nEnsure advanced audit policies for Windows are enabled, specifically:\nObject Access policies [Event ID 4656](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4656) (Handle to an Object was Requested) \n \n``` \nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nSystem Audit Policies > \nObject Access > \nAudit File System (Success,Failure) \nAudit Handle Manipulation (Success,Failure) \n``` \n \nThis event will only trigger if symbolic links are created from a new process spawning cmd.exe or powershell.exe with the correct arguments. \nDirect access to a shell and calling symbolic link creation tools will not generate an event matching this rule. \n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\",\"process_created\") and \n process.pe.original_file_name in (\"Cmd.Exe\",\"PowerShell.EXE\") and \n \n /* Create Symbolic Link to Shadow Copies */\n process.args : (\"*mklink*\", \"*SymbolicLink*\") and process.command_line : (\"*HarddiskVolumeShadowCopy*\")\n", "references": [ "https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/mklink", @@ -53,5 +53,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_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json index 791cfb1c3fce1..b1963b1b83666 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -61,5 +61,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 11 + "version": 13 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json index f2e4f9e740a4a..f542cf2ca89f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of AmsiEnable Registry Key", - "note": "## Triage and analysis\n\n### Investigating Modification of AmsiEnable Registry Key\n\nThe Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and\nservices to integrate with any antimalware product that's present on a machine. AMSI provides integration with multiple\nWindows components, ranging from User Account Control (UAC) to VBA Macros.\n\nSince AMSI is widely used across security products for increased visibility, attackers can disable it to evade\ndetections that rely on it.\n\nThis rule monitors the modifications to the Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable registry key.\n\n#### Possible investigation steps\n\n- Identify the user that performed the action.\n- Check whether this user should be doing this kind of activity.\n- Investigate program execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Investigate the execution of scripts and macros after the registry modification.\n- Retrieve script/office files:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the host to malware infections.\n\n### Related rules\n\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If malware was found, implement temporary network rules, procedures, and segmentation required to contain it.\n- Delete or set the key to its default value.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Modification of AmsiEnable Registry Key\n\nThe Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and\nservices to integrate with any antimalware product that's present on a machine. AMSI provides integration with multiple\nWindows components, ranging from User Account Control (UAC) to VBA Macros.\n\nSince AMSI is widely used across security products for increased visibility, attackers can disable it to evade\ndetections that rely on it.\n\nThis rule monitors the modifications to the Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable registry key.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate the execution of scripts and macros after the registry modification.\n- Retrieve scripts or Microsoft Office files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the host to malware infections.\n\n### Related rules\n\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Delete or set the key to its default value.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\"\n ) and\n registry.data.strings: (\"0\", \"0x00000000\")\n", "references": [ "https://hackinparis.com/data/slides/2019/talks/HIP2019-Dominic_Chell-Cracking_The_Perimeter_With_Sharpshooter.pdf", @@ -54,5 +54,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/defense_evasion_attempt_to_disable_gatekeeper.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json index 9bb48361ccf89..8a1f1482e603a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json @@ -14,7 +14,7 @@ "query": "event.category:process and event.type:(start or process_started) and \n process.args:(spctl and \"--master-disable\")\n", "references": [ "https://support.apple.com/en-us/HT202491", - "https://www.carbonblack.com/blog/tau-threat-intelligence-notification-new-macos-malware-variant-of-shlayer-osx-discovered/" + "https://community.carbonblack.com/t5/Threat-Advisories-Documents/TAU-TIN-Shlayer-OSX/ta-p/68397" ], "risk_score": 47, "rule_id": "4da13d6e-904f-4636-81d8-6ab14b4e6ae9", @@ -45,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/defense_evasion_clearing_windows_console_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json index 9744a5f6ee472..28958ec2d724f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json @@ -12,10 +12,10 @@ "language": "eql", "license": "Elastic License v2", "name": "Clearing Windows Console History", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Clearing Windows Console History\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can try to cover their tracks by clearing PowerShell console history. PowerShell has two different ways of\nlogging commands: the built-in history and the command history managed by the PSReadLine module. This rule looks for the\nexecution of commands that can clear the built-in PowerShell logs or delete the `ConsoleHost_history.txt` file.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n - Verify if any other anti-forensics behaviors were observed.\n- Investigate the PowerShell logs on the SIEM to determine if there was suspicious behavior that an attacker may be\ntrying to cover up.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n - Ensure that PowerShell auditing policies and log collection are in place to grant future visibility.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.action == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name == \"PowerShell.EXE\") and\n (process.args : \"*Clear-History*\" or\n (process.args : (\"*Remove-Item*\", \"rm\") and process.args : (\"*ConsoleHost_history.txt*\", \"*(Get-PSReadlineOption).HistorySavePath*\")) or\n (process.args : \"*Set-PSReadlineOption*\" and process.args : \"*SaveNothing*\"))\n", "references": [ - "https://stefanos.cloud/blog/kb/how-to-clear-the-powershell-command-history/", + "https://stefanos.cloud/kb/how-to-clear-the-powershell-command-history/", "https://www.shellhacks.com/clear-history-powershell/", "https://community.sophos.com/sophos-labs/b/blog/posts/powershell-command-history-forensics" ], @@ -55,5 +55,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_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 2a807abdb6856..ce68ce01ae29b 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Clearing Windows Event Logs", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Clearing Windows Event Logs\n\nWindows event logs are a fundamental data source for security monitoring, forensics, and incident response. Adversaries\ncan tamper, clear, and delete this data to break SIEM detections, cover their tracks, and slow down incident response.\n\nThis rule looks for the execution of the `wevtutil.exe` utility or the `Clear-EventLog` cmdlet to clear event logs.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n - Verify if any other anti-forensics behaviors were observed.\n- Investigate the event logs prior to the action for suspicious behaviors that an attacker may be trying to cover up.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity\nand there are justifications for this action.\n- Analyze whether the cleared event log is pertinent to security and general monitoring. Administrators can clear\nnon-relevant event logs using this mechanism. If this activity is expected and noisy in your environment, consider\nadding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n - This activity is potentially done after the adversary achieves its objectives on the host. Ensure that previous\n actions, if any, are investigated accordingly with their response playbooks.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"process_started\", \"start\") and\n (process.name : \"wevtutil.exe\" or process.pe.original_file_name == \"wevtutil.exe\") and\n process.args : (\"/e:false\", \"cl\", \"clear-log\") or\n process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") and process.args : \"Clear-EventLog\"\n", "risk_score": 21, "rule_id": "d331bbe2-6db4-4941-80a5-8270db72eb61", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 12 + "version": 13 } 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 c79afb655744e..3989ceed2bab9 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 @@ -12,6 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Windows Event Logs Cleared", + "note": "## Triage and analysis\n\n### Investigating Windows Event Logs Cleared\n\nWindows event logs are a fundamental data source for security monitoring, forensics, and incident response. Adversaries\ncan tamper, clear, and delete this data to break SIEM detections, cover their tracks, and slow down incident response.\n\nThis rule looks for the occurrence of clear actions on the `security` event log.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n - Verify if any other anti-forensics behaviors were observed.\n- Investigate the event logs prior to the action for suspicious behaviors that an attacker may be trying to cover up.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n - This activity is potentially done after the adversary achieves its objectives on the host. Ensure that previous\n actions, if any, are investigated accordingly with their response playbooks.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.action:(\"audit-log-cleared\" or \"Log clear\")\n", "risk_score": 21, "rule_id": "45ac4800-840f-414c-b221-53dd36a5aaf7", @@ -49,5 +50,5 @@ ], "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/defense_evasion_code_injection_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json index 85b5c5d897e0f..de61101b8699f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_code_injection_conhost.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious Process from Conhost", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious Process from Conhost\n\nThe Windows Console Host, or `conhost.exe`, is both the server application for all of the Windows Console APIs as well as\nthe classic Windows user interface for working with command-line applications.\n\nThe `conhost.exe` process doesn't normally have child processes. Any processes spawned by the `conhost.exe` process can indicate code\ninjection activity or a suspicious process masquerading as the `conhost.exe` process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### Related rules\n\n- Conhost Spawned By Suspicious Parent Process - 05b358de-aa6d-4f6c-89e6-78f74018b43b\n- Suspicious PowerShell Engine ImageLoad - 852c1f19-68e8-43a6-9dce-340771fe1be3\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"conhost.exe\" and\n not process.executable : (\"?:\\\\Windows\\\\splwow64.exe\", \"?:\\\\Windows\\\\System32\\\\WerFault.exe\", \"?:\\\\Windows\\\\System32\\\\conhost.exe\")\n", "references": [ "https://modexp.wordpress.com/2018/09/12/process-injection-user-data/", @@ -47,5 +47,5 @@ ], "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_defender_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json index fadc8651b726c..7674d0a4db706 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Defender Disabled via Registry Modification", - "note": "## Triage and analysis\n\nDetections should be investigated to identify if the hosts and users are authorized to use this tool. As this rule detects post-exploitation process activity, investigations into this should be prioritized.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Windows Defender Disabled via Registry Modification\n\nMicrosoft Windows Defender is an antivirus product built into Microsoft Windows, which makes it popular across multiple\nenvironments. Disabling it is a common step in threat actor playbooks.\n\nThis rule monitors the registry for configurations that disable Windows Defender or the start of its service.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if this operation is done under change management and approved according to the organization's policy.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity,\nthe configuration is justified (for example, it is being used to deploy other security solutions or troubleshooting),\nand no other suspicious activity has been observed.\n\n### Related rules\n\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Re-enable Windows Defender and restore the service configurations to automatic start.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type in (\"creation\", \"change\") and\n (\n (\n registry.path:\"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings: (\"1\", \"0x00000001\")\n ) or\n (\n registry.path:\"HKLM\\\\System\\\\*ControlSet*\\\\Services\\\\WinDefend\\\\Start\" and\n registry.data.strings in (\"3\", \"4\", \"0x00000003\", \"0x00000004\")\n )\n )\n", "references": [ "https://thedfirreport.com/2020/12/13/defender-control/" @@ -58,5 +58,5 @@ ], "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_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 0509138cf5238..3ea80655c3559 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\n### Investigating Windows Defender Exclusions Added via PowerShell\n\nMicrosoft Windows Defender is an antivirus product built into 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\nthe more notable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/)\nwas observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defender to avoid detection. \n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Examine the exclusion in order to determine the intent behind it.\n- Check for similar behavior in other hosts on the environment.\n- If the exclusion specifies a suspicious file, retrieve it and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives due to how often network administrators legitimately configure\nexclusions. In order to validate the activity further, review the specific exclusion and its intent. There are many\nlegitimate reasons for exclusions, so it's important to gain context.\n\n### Related rules\n\n- Windows Defender Disabled via Registry Modification - 2ffa1f1e-b6db-47fa-994b-1512743847eb\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the identified indicators of compromise (IoCs).\n - Remove and block malicious artifacts identified on the triage.\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Windows Defender Exclusions Added via PowerShell\n\nMicrosoft Windows Defender is an antivirus product built into 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\nthe more notable [examples](https://www.cyberbit.com/blog/endpoint-security/latest-trickbot-variant-has-new-tricks-up-its-sleeve/)\nwas observed in 2018 where Trickbot incorporated mechanisms to disable Windows Defender to avoid detection. \n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Examine the exclusion in order to determine the intent behind it.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- If the exclusion specifies a suspicious file or path, retrieve the file(s) and determine if malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives due to how often network administrators legitimately configure\nexclusions. In order to validate the activity further, review the specific exclusion and its intent. There are many\nlegitimate reasons for exclusions, so it's important to gain context.\n\n### Related rules\n\n- Windows Defender Disabled via Registry Modification - 2ffa1f1e-b6db-47fa-994b-1512743847eb\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Exclusion lists for antimalware capabilities should always be routinely monitored for review.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name in (\"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\")) and\n process.args : (\"*Add-MpPreference*\", \"*Set-MpPreference*\") and\n process.args : (\"*-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": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json index 5cebabb176592..7564c5adbae05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Tampering of Bash Command-Line History", "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", - "query": "process where event.type in (\"start\", \"process_started\") and\n (\n (process.args : (\"rm\", \"echo\") and process.args : (\".bash_history\", \"/root/.bash_history\", \"/home/*/.bash_history\")) or\n (process.name : \"history\" and process.args : \"-c\") or\n (process.args : \"export\" and process.args : (\"HISTFILE=/dev/null\", \"HISTFILESIZE=0\")) or\n (process.args : \"unset\" and process.args : \"HISTFILE\") or\n (process.args : \"set\" and process.args : \"history\" and process.args : \"+o\")\n )\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n (\n ((process.args : (\"rm\", \"echo\") or\n (process.args : \"ln\" and process.args : \"-sf\" and process.args : \"/dev/null\") or\n (process.args : \"truncate\" and process.args : \"-s0\"))\n and process.args : (\".bash_history\", \"/root/.bash_history\", \"/home/*/.bash_history\",\"/Users/.bash_history\", \"/Users/*/.bash_history\",\n \".zsh_history\", \"/root/.zsh_history\", \"/home/*/.zsh_history\", \"/Users/.zsh_history\", \"/Users/*/.zsh_history\")) or\n (process.name : \"history\" and process.args : \"-c\") or\n (process.args : \"export\" and process.args : (\"HISTFILE=/dev/null\", \"HISTFILESIZE=0\")) or\n (process.args : \"unset\" and process.args : \"HISTFILE\") or\n (process.args : \"set\" and process.args : \"history\" and process.args : \"+o\")\n )\n", "risk_score": 47, "rule_id": "7bcbb3ac-e533-41ad-a612-d6c3bf666aba", "severity": "medium", @@ -20,6 +20,7 @@ "Elastic", "Host", "Linux", + "macOS", "Threat Detection", "Defense Evasion" ], @@ -49,5 +50,5 @@ ], "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_disable_posh_scriptblocklogging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json index 96430e3705ce1..32f71856adf56 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "PowerShell Script Block Logging Disabled", - "note": "## Triage and analysis\n\n### Investigating PowerShell Script Block Logging Disabled\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, making\nit available in various environments and creating an attractive way for attackers to execute code.\n\nPowerShell Script Block Logging is a feature of PowerShell that records the content of all script blocks that it\nprocesses, giving defenders visibility of PowerShell scripts and sequences of executed commands.\n\n#### Possible investigation steps\n\n- Identify the user account which performed the action.\n- Check whether the account should perform this kind of action.\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check whether it makes sense for the user to use PowerShell to complete its tasks.\n- Investigate if PowerShell scripts were run after logging was disabled.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Review the implicated user account's privileges.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell Script Block Logging Disabled\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, making\nit available in various environments and creating an attractive way for attackers to execute code.\n\nPowerShell Script Block Logging is a feature of PowerShell that records the content of all script blocks that it\nprocesses, giving defenders visibility of PowerShell scripts and sequences of executed commands.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether it makes sense for the user to use PowerShell to complete tasks.\n- Investigate if PowerShell scripts were run after logging was disabled.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type == \"change\" and\n registry.path : \n \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\PowerShell\\\\ScriptBlockLogging\\\\EnableScriptBlockLogging\"\n and registry.data.strings : (\"0\", \"0x00000000\")\n", "references": [ "https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.PowerShell::EnableScriptBlockLogging" @@ -53,5 +53,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 d8901907381ec..6cb54ed9c048c 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Disable Windows Firewall Rules via Netsh", - "note": "## Triage and analysis\n\n### Investigating Disable Windows Firewall Rules via Netsh\n\nThe Windows Defender Firewall is a native component which provides host-based, two-way network traffic filtering for a\ndevice, and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can disable firewall rules which are intended to prevent lateral movement and command and control traffic to\nenable their operations.\n\nThis rule identifies patterns related to disabling firewall rules using the `netsh.exe` utility.\n\n#### Possible investigation steps\n\n- Identify the user account which performed the action and whether it should perform this kind of action.\n- Contact the user to check if they are aware of the operation.\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Analyze the executed command to determine what it allowed.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user is legitimately performing this kind of activity.\n- Assess the need to disable the modification of the rule, and whether these actions expose the environment to\nunnecessary risks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Evaluate exceptions that can be added to the firewall rule and re-enable the rule.\n- Review the implicated account's privileges.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Disable Windows Firewall Rules via Netsh\n\nThe Windows Defender Firewall is a native component which provides host-based, two-way network traffic filtering for a\ndevice, and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can disable the Windows firewall or its rules to enable lateral movement and command and control activity.\n\nThis rule identifies patterns related to disabling the Windows firewall or its rules using the `netsh.exe` utility.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user to check if they are aware of the operation.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user is an administrator and is legitimately performing\ntroubleshooting.\n- In case of an allowed benign true positive (B-TP), assess adding rules to allow needed traffic and re-enable the firewall.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : \"netsh.exe\" and\n (process.args : \"disable\" and process.args : \"firewall\" and process.args : \"set\") or\n (process.args : \"advfirewall\" and process.args : \"off\" and process.args : \"state\")\n", "risk_score": 47, "rule_id": "4b438734-3793-4fda-bd42-ceeada0be8f9", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 11 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json index 22dcb355cd2fe..588e4246a7886 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Disabling Windows Defender Security Settings via PowerShell", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Disabling Windows Defender Security Settings via PowerShell\n\nMicrosoft Windows Defender is an antivirus product built into Microsoft Windows, which makes it popular across multiple\nenvironments. Disabling it is a common step in threat actor playbooks.\n\nThis rule monitors the execution of commands that can tamper the Windows Defender antivirus features.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Examine the command line to determine which action was executed. Based on that, examine exceptions, antivirus state,\nsample submission, etc.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity,\nthe configuration is justified (for example, it is being used to deploy other security solutions or troubleshooting),\nand no other suspicious activity has been observed.\n\n### Related rules\n\n- Windows Defender Disabled via Registry Modification - 2ffa1f1e-b6db-47fa-994b-1512743847eb\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Based on the command line, take actions to restore the appropriate Windows Defender antivirus configurations.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name in (\"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\")) and\n process.args : \"Set-MpPreference\" and process.args : (\"-Disable*\", \"Disabled\", \"NeverSend\", \"-Exclusion*\")\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/defender/set-mppreference?view=windowsserver2019-ps" @@ -56,5 +56,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_disabling_windows_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json index b92d59f930fb4..74d28465bf9d8 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 @@ -14,10 +14,11 @@ "language": "eql", "license": "Elastic License v2", "name": "Disable Windows Event and Security Logs Using Built-in Tools", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Disable Windows Event and Security Logs Using Built-in Tools\n\nWindows event logs are a fundamental data source for security monitoring, forensics, and incident response. Adversaries\ncan tamper, clear, and delete this data to break SIEM detections, cover their tracks, and slow down incident response.\n\nThis rule looks for the usage of different utilities to disable the EventLog service or specific event logs.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n - Verify if any other anti-forensics behaviors were observed.\n- Investigate the event logs prior to the action for suspicious behaviors that an attacker may be trying to cover up.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Re-enable affected logging components, services, and security monitoring.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n\n ((process.name:\"logman.exe\" or process.pe.original_file_name == \"Logman.exe\") and\n process.args : \"EventLog-*\" and process.args : (\"stop\", \"delete\")) or\n\n ((process.name : (\"pwsh.exe\", \"powershell.exe\", \"powershell_ise.exe\") or process.pe.original_file_name in\n (\"pwsh.exe\", \"powershell.exe\", \"powershell_ise.exe\")) and\n\tprocess.args : \"Set-Service\" and process.args: \"EventLog\" and process.args : \"Disabled\") or\n\t\n ((process.name:\"auditpol.exe\" or process.pe.original_file_name == \"AUDITPOL.EXE\") and process.args : \"/success:disable\")\n", "references": [ - "https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/logman" + "https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/logman", + "https://medium.com/palantir/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63" ], "risk_score": 21, "rule_id": "4de76544-f0e5-486a-8f84-eae0b6063cdc", @@ -49,11 +50,23 @@ "reference": "https://attack.mitre.org/techniques/T1070/001/" } ] + }, + { + "id": "T1562", + "name": "Impair Defenses", + "reference": "https://attack.mitre.org/techniques/T1562/", + "subtechnique": [ + { + "id": "T1562.006", + "name": "Indicator Blocking", + "reference": "https://attack.mitre.org/techniques/T1562/006/" + } + ] } ] } ], "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_elastic_agent_service_terminated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json new file mode 100644 index 0000000000000..e7dc47dcbd109 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the Elastic endpoint agent has stopped and is no longer running on the host. Adversaries may attempt to disable security monitoring tools in an attempt to evade detection or prevention capabilities during an intrusion. This may also indicate an issue with the agent itself and should be addressed to ensure defensive measures are back in a stable state.", + "from": "now-9m", + "index": [ + "logs-*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Elastic Agent Service Terminated", + "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "query": "process where\n/* net, sc or wmic stopping or deleting Elastic Agent on Windows */\n(event.type == \"start\" and\n process.name : (\"net.exe\", \"sc.exe\", \"wmic.exe\",\"powershell.exe\",\"taskkill.exe\",\"PsKill.exe\",\"ProcessHacker.exe\") and \n process.args : (\"stopservice\",\"uninstall\", \"stop\", \"disabled\",\"Stop-Process\",\"terminate\",\"suspend\") and\n process.args : (\"elasticendpoint\", \"Elastic Agent\",\"elastic-agent\",\"elastic-endpoint\")) \nor\n/* service or systemctl used to stop Elastic Agent on Linux */\n(event.type == \"end\" and\n (process.name : (\"systemctl\",\"service\") and \n process.args : (\"elastic-agent\", \"stop\")) \n or \n /* Unload Elastic Agent extension on MacOS */\n (process.name : \"kextunload\" and\n process.args : \"com.apple.iokit.EndpointSecurity\" and \n event.action : \"end\"))\n", + "risk_score": 47, + "rule_id": "b627cd12-dac4-11ec-9582-f661ea17fbcd", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Windows", + "macOS", + "Threat Detection", + "Defense Evasion" + ], + "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": "eql", + "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 98c13db978a53..455f652b29bb3 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote Desktop Enabled in Windows Firewall by Netsh", - "note": "## Triage and analysis\n\n### Investigating Remote Desktop Enabled in Windows Firewall by Netsh\n\nMicrosoft Remote Desktop Protocol (RDP) is a proprietary Microsoft protocol that enables remote connections to other\ncomputers, typically over TCP port 3389.\n\nAttackers can use RDP to conduct their actions interactively. Ransomware operators frequently use RDP to access\nvictim servers, often using privileged accounts.\n\nThis rule detects the creation of a Windows Firewall inbound rule that would allow inbound RDP traffic using the\n`netsh.exe` utility.\n\n#### Possible investigation steps\n\n- Identify the user account which performed the action and whether it should perform this kind of action.\n- Contact the user to check if they are aware of the operation.\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check whether it makes sense to enable RDP to this host, given its role in the environment.\n- Check if the host is directly exposed to the internet.\n- Check whether privileged accounts accessed the host shortly after the modification.\n\n### False positive analysis\n\n- The `netsh.exe` utility can be used legitimately. Check whether the user should be performing this kind of activity, whether the user is aware\nof it, whether RDP should be open, and whether the action exposes the environment to unnecessary risks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If RDP is needed, make sure to secure it:\n - Allowlist RDP traffic to specific trusted hosts.\n - Restrict RDP logins to authorized non-administrator accounts, where possible.\n- Quarantine the implicated host to prevent further post-compromise behavior.\n- Review the implicated account's privileges.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Remote Desktop Enabled in Windows Firewall by Netsh\n\nMicrosoft Remote Desktop Protocol (RDP) is a proprietary Microsoft protocol that enables remote connections to other\ncomputers, typically over TCP port 3389.\n\nAttackers can use RDP to conduct their actions interactively. Ransomware operators frequently use RDP to access\nvictim servers, often using privileged accounts.\n\nThis rule detects the creation of a Windows Firewall inbound rule that would allow inbound RDP traffic using the\n`netsh.exe` utility.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user to check if they are aware of the operation.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether it makes sense to enable RDP to this host, given its role in the environment.\n- Check if the host is directly exposed to the internet.\n- Check whether privileged accounts accessed the host shortly after the modification.\n- Review network events within a short timespan of this alert for incoming RDP connection attempts.\n\n### False positive analysis\n\n- The `netsh.exe` utility can be used legitimately. Check whether the user should be performing this kind of activity, whether the user is aware\nof it, whether RDP should be open, and whether the action exposes the environment to unnecessary risks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If RDP is needed, make sure to secure it:\n - Allowlist RDP traffic to specific trusted hosts.\n - Restrict RDP logins to authorized non-administrator accounts, where possible.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"netsh.exe\" or process.pe.original_file_name == \"netsh.exe\") and\n process.args : (\"localport=3389\", \"RemoteDesktop\", \"group=\\\"remote desktop\\\"\") and\n process.args : (\"action=allow\", \"enable=Yes\", \"enable\")\n", "risk_score": 47, "rule_id": "074464f9-f30d-4029-8c03-0ed237fffec7", @@ -50,5 +50,5 @@ ], "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_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 eff4d1dac1415..7f56edc51c96a 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 @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enable Host Network Discovery via Netsh", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Enable Host Network Discovery via Netsh\n\nThe Windows Defender Firewall is a native component that provides host-based, two-way network traffic filtering for a\ndevice and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can enable Network Discovery on the Windows firewall to find other systems present in the same network. Systems\nwith this setting enabled will communicate with other systems using broadcast messages, which can be used to identify\ntargets for lateral movement. This rule looks for the setup of this setting using the netsh utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity\nand there are justifications for this configuration.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Disable Network Discovery:\n - Using netsh: `netsh advfirewall firewall set rule group=\"Network Discovery\" new enable=No`\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type == \"start\" and\nprocess.name : \"netsh.exe\" and\nprocess.args : (\"firewall\", \"advfirewall\") and process.args : \"group=Network Discovery\" and process.args : \"enable=Yes\"\n", "risk_score": 47, "rule_id": "8b4f0816-6a65-4630-86a6-c21c179c0d09", @@ -53,5 +53,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_execution_lolbas_wuauclt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json index a4608867ecde7..00adb53d10e3b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json @@ -48,5 +48,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json index 2a7603e57117c..da2a1eb166dd5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Microsoft Windows Defender Tampering", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Microsoft Windows Defender Tampering\n\nMicrosoft Windows Defender is an antivirus product built into Microsoft Windows, which makes it popular across multiple\nenvironments. Disabling it is a common step in threat actor playbooks.\n\nThis rule monitors the registry for modifications that disable Windows Defender features.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Examine which features have been disabled, and check if this operation is done under change management and approved\naccording to the organization's policy.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity,\nthe configuration is justified (for example, it is being used to deploy other security solutions or troubleshooting),\nand no other suspicious activity has been observed.\n\n### Related rules\n\n- Windows Defender Disabled via Registry Modification - 2ffa1f1e-b6db-47fa-994b-1512743847eb\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Take actions to restore the appropriate Windows Defender antivirus configurations.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type in (\"creation\", \"change\") and\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\PUAProtection\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender Security Center\\\\App and Browser protection\\\\DisallowExploitProtectionOverride\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Features\\\\TamperProtection\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableRealtimeMonitoring\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableIntrusionPreventionSystem\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableScriptScanning\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Windows Defender Exploit Guard\\\\Controlled Folder Access\\\\EnableControlledFolderAccess\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableIOAVProtection\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Reporting\\\\DisableEnhancedNotifications\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\DisableBlockAtFirstSeen\" and\n registry.data.strings : (\"1\", \"0x00000001\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\SpynetReporting\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\SpyNet\\\\SubmitSamplesConsent\" and\n registry.data.strings : (\"0\", \"0x00000000\")) or\n (registry.path : \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\Real-Time Protection\\\\DisableBehaviorMonitoring\" and\n registry.data.strings : (\"1\", \"0x00000001\"))\n", "references": [ "https://thedfirreport.com/2021/10/18/icedid-to-xinglocker-ransomware-in-24-hours/", @@ -56,5 +56,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_ms_office_suspicious_regmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json index 4fc39214636fa..71e18a0268091 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "MS Office Macro Security Registry Modifications", - "note": "## Triage and analysis\n\n### Investigating MS Office Macro Security Registry Modifications\n\nMacros are small programs that are used to automate repetitive tasks in Microsoft Office applications.\nHistorically, macros have been used for a variety of reasons -- from automating part of a job, to\nbuilding entire processes and data flows. Macros are written in Visual Basic for Applications (VBA) and are saved as\npart of Microsoft Office files.\n\nMacros are often created for legitimate reasons, but they can also be written by attackers to gain access, harm a\nsystem, or bypass other security controls such as application allow listing. In fact, exploitation from malicious macros\nis one of the top ways that organizations are compromised today. These attacks are often conducted through phishing or\nspear phishing campaigns.\n\nAttackers can convince victims to modify Microsoft Office security settings, so their macros are trusted by default and\nno warnings are displayed when they are executed. These settings include:\n\n* *Trust access to the VBA project object model* - When enabled, Microsoft Office will trust all macros and run any code\nwithout showing a security warning or requiring user permission.\n* *VbaWarnings* - When set to 1, Microsoft Office will trust all macros and run any code without showing a security\nwarning or requiring user permission.\n\nThis rule looks for registry changes affecting the conditions above.\n\n#### Possible investigation steps\n\n- Identify the user that performed the operation.\n- Verify whether malicious macros were executed after the registry change.\n- Contact the user and check if the change was done manually.\n- Investigate other alerts associated with the user during the past 48 hours.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true\npositives (B-TPs), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the registry key value.\n- Isolate the host if malicious code was executed and reset the involved account's passwords.\n- Explore using GPOs to manage security settings for Microsoft Office macros.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating MS Office Macro Security Registry Modifications\n\nMacros are small programs that are used to automate repetitive tasks in Microsoft Office applications.\nHistorically, macros have been used for a variety of reasons -- from automating part of a job, to\nbuilding entire processes and data flows. Macros are written in Visual Basic for Applications (VBA) and are saved as\npart of Microsoft Office files.\n\nMacros are often created for legitimate reasons, but they can also be written by attackers to gain access, harm a\nsystem, or bypass other security controls such as application allow listing. In fact, exploitation from malicious macros\nis one of the top ways that organizations are compromised today. These attacks are often conducted through phishing or\nspear phishing campaigns.\n\nAttackers can convince victims to modify Microsoft Office security settings, so their macros are trusted by default and\nno warnings are displayed when they are executed. These settings include:\n\n* *Trust access to the VBA project object model* - When enabled, Microsoft Office will trust all macros and run any code\nwithout showing a security warning or requiring user permission.\n* *VbaWarnings* - When set to 1, Microsoft Office will trust all macros and run any code without showing a security\nwarning or requiring user permission.\n\nThis rule looks for registry changes affecting the conditions above.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user and check if the change was done manually.\n- Verify whether malicious macros were executed after the registry change.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve recently executed Office documents and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true\npositives (B-TPs), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the registry key value.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Explore using GPOs to manage security settings for Microsoft Office macros.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type == \"change\" and\n registry.path : (\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\"\n ) and \n registry.data.strings == \"0x00000001\" and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"mshta.exe\", \"mshta.exe\", \"winword.exe\", \"excel.exe\")\n", "risk_score": 47, "rule_id": "feeed87c-5e95-4339-aef1-47fd79bcfbe3", @@ -64,5 +64,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_posh_assembly_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json index a13aea7c4ee8c..e7df1b3b051a3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json @@ -11,6 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious .NET Reflection via PowerShell", + "note": "## Triage and analysis\n\n### Investigating Suspicious .NET Reflection via PowerShell\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use .NET reflection to load PEs and DLLs in memory. These payloads are commonly embedded in the script,\nwhich can circumvent file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately outside engineering or IT business units. As long as the analyst did\nnot identify malware or suspicious activity related to the user or host, this alert can be dismissed.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n \"[System.Reflection.Assembly]::Load\" or\n \"[Reflection.Assembly]::Load\"\n )\n", "references": [ "https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load" @@ -78,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json index 1bb2fb2bcc8f2..79f4f3a85768b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json @@ -14,6 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Payload Encoded and Compressed", + "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Payload Encoded and Compressed\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can embed compressed and encoded payloads in scripts to load directly into the memory without touching the\ndisk. This strategy can circumvent string and file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately outside engineering or IT business units. As long as the analyst did\nnot identify malware or suspicious activity related to the user or host, this alert can be dismissed.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n (\n \"System.IO.Compression.DeflateStream\" or\n \"System.IO.Compression.GzipStream\" or\n \"IO.Compression.DeflateStream\" or\n \"IO.Compression.GzipStream\"\n ) and\n FromBase64String\n )\n", "risk_score": 47, "rule_id": "81fe9dc6-a2d7-4192-a2d8-eed98afc766a", @@ -71,5 +72,5 @@ ], "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/defense_evasion_posh_process_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json index 361aa370a22a2..ced22c139e48b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Potential Process Injection via PowerShell", - "note": "## Triage and analysis.\n\n### Investigating Potential Process Injection via PowerShell\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nPowerShell also has solid capabilities to make the interaction with the Win32 API in an uncomplicated and reliable way,\nlike the execution of inline C# code, PSReflect, Get-ProcAddress, etc.\n\nRed Team tooling and malware developers take advantage of these capabilities to develop stagers and loaders that inject\npayloads directly into the memory without touching the disk to circumvent file-based security protections.\n\n#### Possible investigation steps\n\n- Examine script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Check if the imported function was executed and which process it targeted.\n- Check if the injected code can be retrieved (hardcoded in the script or on command line logs).\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host for forensic investigation, as well as eradication and recovery activities.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n- Reset the password for the user account.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating Potential Process Injection via PowerShell\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nPowerShell also has solid capabilities to make the interaction with the Win32 API in an uncomplicated and reliable way,\nlike the execution of inline C# code, PSReflect, Get-ProcAddress, etc.\n\nRed Team tooling and malware developers take advantage of these capabilities to develop stagers and loaders that inject\npayloads directly into the memory without touching the disk to circumvent file-based security protections.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Check if the imported function was executed and which process it targeted.\n- Check if the injected code can be retrieved (hardcoded in the script or on command line logs).\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n (VirtualAlloc or VirtualAllocEx or VirtualProtect or LdrLoadDll or LoadLibrary or LoadLibraryA or\n LoadLibraryEx or GetProcAddress or OpenProcess or OpenProcessToken or AdjustTokenPrivileges) and\n (WriteProcessMemory or CreateRemoteThread or NtCreateThreadEx or CreateThread or QueueUserAPC or\n SuspendThread or ResumeThread or GetDelegateForFunctionPointer)\n )\n", "references": [ "https://github.com/EmpireProject/Empire/blob/master/data/module_source/management/Invoke-PSInject.ps1", @@ -62,5 +62,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/defense_evasion_powershell_windows_firewall_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json index 43d5d7adcbccf..f9cf7a5e568c2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Firewall Disabled via PowerShell", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Windows Firewall Disabled via PowerShell\n\nWindows Defender Firewall is a native component that provides host-based, two-way network traffic filtering for a\ndevice and blocks unauthorized network traffic flowing into or out of the local device.\n\nAttackers can disable the Windows firewall or its rules to enable lateral movement and command and control activity.\n\nThis rule identifies patterns related to disabling the Windows firewall or its rules using the `Set-NetFirewallProfile`\nPowerShell cmdlet.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user is an administrator and is legitimately performing\ntroubleshooting.\n- In case of an allowed benign true positive (B-TP), assess adding rules to allow needed traffic and re-enable the firewall.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Re-enable the firewall with its desired configurations.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.action == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name == \"PowerShell.EXE\") and\n process.args : \"*Set-NetFirewallProfile*\" and\n (process.args : \"*-Enabled*\" and process.args : \"*False*\") and\n (process.args : \"*-All*\" or process.args : (\"*Public*\", \"*Domain*\", \"*Private*\"))\n", "references": [ "https://docs.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallprofile?view=windowsserver2019-ps", @@ -59,5 +59,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/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json index 94b082ecd60fc..1cc9aa9f1004a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json @@ -14,7 +14,7 @@ "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and \n process.name:\"scp\" and\n process.args:\"StrictHostKeyChecking=no\" and \n process.command_line:(\"scp *localhost:/*\", \"scp *127.0.0.1:/*\") and\n not process.args:\"vagrant@*127.0.0.1*\"\n", "references": [ - "https://blog.trendmicro.com/trendlabs-security-intelligence/xcsset-mac-malware-infects-xcode-projects-performs-uxss-attack-on-safari-other-browsers-leverages-zero-day-exploits/" + "https://www.trendmicro.com/en_us/research/20/h/xcsset-mac-malware--infects-xcode-projects--uses-0-days.html" ], "risk_score": 73, "rule_id": "c02c8b9f-5e1d-463c-a1b0-04edcdfe1a3d", @@ -61,5 +61,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_proxy_execution_via_msdt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json new file mode 100644 index 0000000000000..b1dd677a10f95 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json @@ -0,0 +1,50 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies potential abuse of the Microsoft Diagnostics Troubleshooting Wizard (MSDT) to proxy malicious command or binary execution via malicious process arguments.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*", + "winlogbeat-*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Suspicious Microsoft Diagnostics Wizard Execution", + "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "query": "process where event.type in (\"start\", \"process_started\") and\n (process.pe.original_file_name == \"msdt.exe\" or process.name : \"msdt.exe\") and\n (\n process.args : (\"IT_RebrowseForFile=*\", \"ms-msdt:/id\", \"ms-msdt:-id\", \"*FromBase64*\") or\n\n (process.args : \"-af\" and process.args : \"/skip\" and \n process.parent.name : (\"explorer.exe\", \"cmd.exe\", \"powershell.exe\", \"cscript.exe\", \"wscript.exe\", \"mshta.exe\", \"rundll32.exe\", \"regsvr32.exe\") and\n process.args : (\"?:\\\\WINDOWS\\\\diagnostics\\\\index\\\\PCWDiagnostic.xml\", \"PCWDiagnostic.xml\", \"?:\\\\Users\\\\Public\\\\*\", \"?:\\\\Windows\\\\Temp\\\\*\")) or\n\n (process.pe.original_file_name == \"msdt.exe\" and not process.name : \"msdt.exe\" and process.name != null) or\n\n (process.pe.original_file_name == \"msdt.exe\" and not process.executable : (\"?:\\\\Windows\\\\system32\\\\msdt.exe\", \"?:\\\\Windows\\\\SysWOW64\\\\msdt.exe\"))\n )\n", + "references": [ + "https://twitter.com/nao_sec/status/1530196847679401984", + "https://lolbas-project.github.io/lolbas/Binaries/Msdt/" + ], + "risk_score": 73, + "rule_id": "2c3c29a4-f170-42f8-a3d8-2ceebc18eb6a", + "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/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json index 40d25d7813a30..557358c470c5e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json @@ -52,5 +52,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 12 + "version": 14 } 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 4d66650aecb9f..177644038b3cc 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 @@ -13,7 +13,7 @@ "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 ancestor and child processes spawned by the process in question for indicators of further malicious code execution.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", - "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", + "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 3+ contiguous whitespace characters */\n process.command_line regex \"([^ ]+[ ]{3,}[^ ]*){3,}.*\"\n", "references": [ "https://twitter.com/JohnLaTwC/status/1419251082736201737" ], @@ -40,5 +40,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_workfolders_control_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json index 9bb002ceecebc..392fd07b55112 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json @@ -11,8 +11,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Signed Proxy Execution via MS WorkFolders", - "note": "## Triage and analysis\n\n### Investigating Signed Proxy Execution via MS WorkFolders\n\nWork Folders is a role service for file servers running Windows Server that provides a consistent way for users to access\ntheir work files from their PCs and devices. This allows users to store work files and access them from anywhere. When\ncalled, Work Folders will automatically execute any Portable Executable (PE) named `control.exe` as an argument before\naccessing the synced share.\n\nUsing Work Folders to execute a masqueraded control.exe could allow an adversary to bypass application controls and\nincrease privileges.\n \n#### Possible investigation steps\n\n- Investigate the process tree starting with parent process WorkFolders.exe and child process control.exe to determine\nif other child processes spawned during execution.\n- Trace the activity related to the `control.exe` binary to identify any continuing intrusion activity on the host.\n- Examine the location of the WorkFolders.exe binary to determine if it was copied to the location of the control.exe\nbinary. It resides in the System32 directory by default.\n- Review the control.exe binary executed with Work Folders to determine maliciousness such as additional host activity\nor network traffic.\n- Determine if control.exe was synced to sync share, indicating potential lateral movement.\n- Review how control.exe was originally delivered on the host, such as emailed, downloaded from the web, or written to\ndisk from a separate binary.\n \n### False positive analysis \n\n- Windows Work Folders are used legitimately by end users and administrators for file sharing and syncing but not in the\ninstance where a suspicious `control.exe` is passed as an argument.\n\n### Response and remediation \n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Review the Work Folders synced share to determine if the 'control.exe' was shared and if so remove it.\n- If no lateral movement was identified during investigation, take the effected host offline if possible and remove the\ncontrol.exe binary as well as any additional artifacts identified during investigation.\n- Review integrating Windows Information Protection (WIP) to enforce data protection by encrypting the data on PCs using\nWork Folders.\n- Confirm with the user whether this was expected or not, and reset their password.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "name": "Signed Proxy Execution via MS Work Folders", + "note": "## Triage and analysis\n\n### Investigating Signed Proxy Execution via MS Work Folders\n\nWork Folders is a role service for file servers running Windows Server that provides a consistent way for users to access\ntheir work files from their PCs and devices. This allows users to store work files and access them from anywhere. When\ncalled, Work Folders will automatically execute any Portable Executable (PE) named control.exe as an argument before\naccessing the synced share.\n\nUsing Work Folders to execute a masqueraded control.exe could allow an adversary to bypass application controls and\nincrease privileges.\n \n#### Possible investigation steps\n\n- Investigate the process tree starting with parent process WorkFolders.exe and child process control.exe to determine\nif other child processes spawned during execution.\n- Trace the activity related to the control.exe binary to identify any continuing intrusion activity on the host.\n- Examine the location of the WorkFolders.exe binary to determine if it was copied to the location of the control.exe\nbinary. It resides in the System32 directory by default.\n- Review the control.exe binary executed with Work Folders to determine maliciousness such as additional host activity\nor network traffic.\n- Determine if control.exe was synced to sync share, indicating potential lateral movement.\n- Review how control.exe was originally delivered on the host, such as emailed, downloaded from the web, or written to\ndisk from a separate binary.\n \n### False positive analysis \n\n- Windows Work Folders are used legitimately by end users and administrators for file sharing and syncing but not in the\ninstance where a suspicious control.exe is passed as an argument.\n\n### Response and remediation \n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Review the Work Folders synced share to determine if the control.exe was shared and if so remove it.\n- If no lateral movement was identified during investigation, take the affected host offline if possible and remove the\ncontrol.exe binary as well as any additional artifacts identified during investigation.\n- Review integrating Windows Information Protection (WIP) to enforce data protection by encrypting the data on PCs using\nWork Folders.\n- Confirm with the user whether this was expected or not, and reset their password.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\",\"process_started\")\n and process.name : \"control.exe\" and process.parent.name : \"WorkFolders.exe\"\n and not process.executable : (\"?:\\\\Windows\\\\System32\\\\control.exe\", \"?:\\\\Windows\\\\SysWOW64\\\\control.exe\")\n", "references": [ "https://docs.microsoft.com/en-us/windows-server/storage/work-folders/work-folders-overview", @@ -48,5 +48,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 26a15cf522b5f..e8f31c677cfc7 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### 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\nfrom Active Directory (AD). Network discovery and enumeration tools like `AdFind` are useful to adversaries in the same\nways they are effective for network administrators. This tool provides quick ability to scope AD person/computer objects\nand understand subnets and domain information. There are many [examples](https://thedfirreport.com/category/adfind/) of\nthis tool being adopted by ransomware and criminal groups and used in compromises.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Examine the command line to determine what information was retrieved by the tool.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives as it is a legitimate tool used by network administrators. One\noption could be allowlisting 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 can be very telling.\n\n### Related rules\n\n- Windows Network Enumeration - 7b8bfc26-81d2-435e-965c-d722ee397ef1\n- Enumeration of Administrator Accounts - 871ea072-1b71-4def-b016-6278b505138d\n- Enumeration Command Spawned via WMIPrvSE - 770e0c4d-b998-41e5-a62e-c7901fd7f470\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Determine the initial infection vector.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "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\nfrom Active Directory (AD). Network discovery and enumeration tools like `AdFind` are useful to adversaries in the same\nways they are effective for network administrators. This tool provides quick ability to scope AD person/computer objects\nand understand subnets and domain information. There are many [examples](https://thedfirreport.com/category/adfind/) of\nthis tool being adopted by ransomware and criminal groups and used in compromises.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Examine the command line to determine what information was retrieved by the tool.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives as it is a legitimate tool used by network administrators.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\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 can be very telling.\n\n### Related rules\n\n- Windows Network Enumeration - 7b8bfc26-81d2-435e-965c-d722ee397ef1\n- Enumeration of Administrator Accounts - 871ea072-1b71-4def-b016-6278b505138d\n- Enumeration Command Spawned via WMIPrvSE - 770e0c4d-b998-41e5-a62e-c7901fd7f470\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\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/", @@ -80,5 +80,5 @@ ], "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_admin_recon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json index abb1d504ad479..5744f96fb3469 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enumeration of Administrator Accounts", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Enumeration of Administrator Accounts\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `net` and `wmic` utilities to enumerate administrator-related users or groups \nin the domain and local machine scope. Attackers can use this information to plan their next steps of the attack, such\nas mapping targets for credential compromise and other post-exploitation activities.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Related rules\n\n- AdFind Command Activity - eda499b8-a073-4e35-9733-22ec71f57f3a\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (((process.name : \"net.exe\" or process.pe.original_file_name == \"net.exe\") or\n ((process.name : \"net1.exe\" or process.pe.original_file_name == \"net1.exe\") and\n not process.parent.name : \"net.exe\")) and\n process.args : (\"group\", \"user\", \"localgroup\") and\n process.args : (\"admin\", \"Domain Admins\", \"Remote Desktop Users\", \"Enterprise Admins\", \"Organization Management\") and\n not process.args : \"/add\")\n\n or\n\n ((process.name : \"wmic.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n process.args : (\"group\", \"useraccount\"))\n", "risk_score": 21, "rule_id": "871ea072-1b71-4def-b016-6278b505138d", @@ -55,5 +55,5 @@ ], "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/discovery_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json new file mode 100644 index 0000000000000..e3455e2d3490c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json @@ -0,0 +1,47 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies when the SYSTEM account uses an account discovery utility. This could be a sign of discovery activity after an adversary has achieved privilege escalation.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Account Discovery Command via SYSTEM Account", + "note": "## Triage and analysis\n\n### Investigating Account Discovery Command via SYSTEM Account\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of account discovery utilities using the SYSTEM account, which is commonly observed\nafter attackers successfully perform privilege escalation or exploit web applications.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n - If the process tree includes a web-application server process such as w3wp, httpd.exe, nginx.exe and alike,\n investigate any suspicious file creation or modification in the last 48 hours to assess the presence of any potential\n webshell backdoor.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine how the SYSTEM account is being used. For example, users with administrator privileges can spawn a system\nshell using Windows services, scheduled tasks or other third party utilities.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n- Use the data collected through the analysis to investigate other machines affected in the environment.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "query": "process where event.type in (\"start\", \"process_started\") and \n (?process.Ext.token.integrity_level_name : \"System\" or\n ?winlog.event_data.IntegrityLevel : \"System\") and\n (process.name : \"whoami.exe\" or\n (process.name : \"net1.exe\" and not process.parent.name : \"net.exe\"))\n", + "risk_score": 21, + "rule_id": "2856446a-34e6-435b-9fb5-f8f040bfa7ed", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Discovery" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1033", + "name": "System Owner/User Discovery", + "reference": "https://attack.mitre.org/techniques/T1033/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 12 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json index 0134bdb26cc37..87f6d713d81fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_file_dir_discovery.json @@ -15,6 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "File and Directory Discovery", + "note": "## Triage and analysis\n\n### Investigating File and Directory Discovery\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for three directory-listing commands in one minute, which can indicate attempts to locate valuable files,\nspecific file types or installed programs.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by agent.id, user.name with maxspan=1m\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n[process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"cmd.exe\" or process.pe.original_file_name == \"Cmd.Exe\") and process.args : \"dir\") or\n process.name : \"tree.com\"]\n", "risk_score": 21, "rule_id": "7b08314d-47a0-4b71-ae4e-16544176924f", @@ -44,5 +45,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_hping_activity.json similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_hping_activity.json index 3a91b205e9da3..8f8bb7c4f2e31 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_hping_activity.json @@ -25,9 +25,27 @@ "Elastic", "Host", "Linux", - "Threat Detection" + "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": "query", - "version": 7 + "version": 8 } 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 deleted file mode 100644 index 7f167f0da268e..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_command_system_account.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies when the SYSTEM account uses an account discovery utility. This could be a sign of discovery activity after an adversary has achieved privilege escalation.", - "from": "now-9m", - "index": [ - "winlogbeat-*", - "logs-endpoint.events.*", - "logs-windows.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Account Discovery Command via SYSTEM Account", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", - "query": "process where event.type in (\"start\", \"process_started\") and \n (?process.Ext.token.integrity_level_name : \"System\" or\n ?winlog.event_data.IntegrityLevel : \"System\") and\n (process.name : \"whoami.exe\" or\n (process.name : \"net1.exe\" and not process.parent.name : \"net.exe\"))\n", - "risk_score": 21, - "rule_id": "2856446a-34e6-435b-9fb5-f8f040bfa7ed", - "severity": "low", - "tags": [ - "Elastic", - "Host", - "Windows", - "Threat Detection", - "Discovery" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0007", - "name": "Discovery", - "reference": "https://attack.mitre.org/tactics/TA0007/" - }, - "technique": [ - { - "id": "T1033", - "name": "System Owner/User Discovery", - "reference": "https://attack.mitre.org/techniques/T1033/" - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 11 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json index 654bfc284106c..1cdb1c5d8fb6d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Network Enumeration", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Windows Network Enumeration\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `net` utility to enumerate servers in the environment that hosts shared drives\nor printers. This information is useful to attackers as they can identify targets for lateral movements and search for\nvaluable shared data.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n ((process.name : \"net.exe\" or process.pe.original_file_name == \"net.exe\") or\n ((process.name : \"net1.exe\" or process.pe.original_file_name == \"net1.exe\") and\n not process.parent.name : \"net.exe\")) and\n (process.args : \"view\" or (process.args : \"time\" and process.args : \"\\\\\\\\*\"))\n\n\n /* expand when ancestry is available\n and not descendant of [process where event.type == (\"start\", \"process_started\") and process.name : \"cmd.exe\" and\n ((process.parent.name : \"userinit.exe\") or\n (process.parent.name : \"gpscript.exe\") or\n (process.parent.name : \"explorer.exe\" and\n process.args : \"C:\\\\*\\\\Start Menu\\\\Programs\\\\Startup\\\\*.bat*\"))]\n */\n", "risk_score": 47, "rule_id": "7b8bfc26-81d2-435e-965c-d722ee397ef1", @@ -48,5 +48,5 @@ ], "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/discovery_peripheral_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json index 146602a9a8d9b..3b6f09e4d1ac1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Identifies use of the Windows file system utility (fsutil.exe ) to gather information about attached peripheral devices and components connected to a computer system.", + "description": "Identifies use of the Windows file system utility (fsutil.exe) to gather information about attached peripheral devices and components connected to a computer system.", "from": "now-9m", "index": [ "winlogbeat-*", @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Peripheral Device Discovery", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Peripheral Device Discovery\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `fsutil` utility with the `fsinfo` subcommand to enumerate drives attached to\nthe computer, which can be used to identify secondary drives used for backups, mapped network drives, and removable\nmedia. These devices can contain valuable information for attackers.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n- Determine whether this activity was followed by suspicious file access/copy operations or uploads to file storage\nservices.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"fsutil.exe\" or process.pe.original_file_name == \"fsutil.exe\") and \n process.args : \"fsinfo\" and process.args : \"drives\"\n", "risk_score": 21, "rule_id": "0c7ca5c2-728d-4ad9-b1c5-bbba83ecb1f4", @@ -43,5 +43,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/discovery_posh_suspicious_api_functions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json index 6fc58f9dc4e9f..c5423d6c13eb4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell Suspicious Discovery Related Windows API Functions", - "note": "## Triage and analysis.\n\n### Investigating PowerShell Suspicious Discovery Related Windows API Functions\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell to interact with the Win32 API to bypass command line based detections, using libraries\nlike PSReflect or Get-ProcAddress Cmdlet.\n\n#### Possible investigation steps\n\n- Examine script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Check if the imported function was executed.\n\n### False positive analysis\n\n- Discovery activities themselves are not inherently malicious if occurring in isolation, as long as the script does not\ncontain other capabilities, and there are no other alerts related to the user or host; such alerts can be dismissed.\nHowever, analysts should keep in mind that this is not a common way of getting information, making it suspicious.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell Suspicious Discovery Related Windows API Functions\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell to interact with the Win32 API to bypass command line based detections, using libraries\nlike PSReflect or Get-ProcAddress Cmdlet.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Examine file or network events from the involved PowerShell process for suspicious behavior.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Check for additional PowerShell and command-line logs that indicate that imported functions were run.\n\n### False positive analysis\n\n- Discovery activities themselves are not inherently malicious if occurring in isolation, as long as the script does not\ncontain other capabilities, and there are no other alerts related to the user or host; such alerts can be dismissed.\nHowever, analysts should keep in mind that this is not a common way of getting information, making it suspicious.\n\n### Related rules\n\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n NetShareEnum or\n NetWkstaUserEnum or\n NetSessionEnum or\n NetLocalGroupEnum or\n NetLocalGroupGetMembers or\n DsGetSiteName or\n DsEnumerateDomainTrusts or\n WTSEnumerateSessionsEx or\n WTSQuerySessionInformation or\n LsaGetLogonSessionData or\n QueryServiceObjectSecurity\n )\n", "references": [ "https://github.com/BC-SECURITY/Empire/blob/9259e5106986847d2bb770c4289c0c0f1adf2344/data/module_source/situational_awareness/network/powerview.ps1#L21413", @@ -88,5 +88,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json index 1e687851af5f3..6783b8053b157 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json @@ -13,6 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "External IP Lookup from Non-Browser Process", + "note": "## Triage and analysis\n\n### Investigating External IP Lookup from Non-Browser Process\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for connections to known IP lookup services through non-browser processes or non-installed programs.\nUsing only the IP address of the compromised system, attackers can obtain valuable information such as the system's\ngeographic location, the company that owns the IP, whether the system is cloud-hosted, and more.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Use the data collected through the analysis to investigate other machines affected in the environment.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "network where network.protocol == \"dns\" and\n process.name != null and user.id not in (\"S-1-5-19\", \"S-1-5-20\") and\n event.action == \"lookup_requested\" and\n /* Add new external IP lookup services here */\n dns.question.name :\n (\n \"*api.ipify.org\",\n \"*freegeoip.app\",\n \"*checkip.amazonaws.com\",\n \"*checkip.dyndns.org\",\n \"*freegeoip.app\",\n \"*icanhazip.com\",\n \"*ifconfig.*\",\n \"*ipecho.net\",\n \"*ipgeoapi.com\",\n \"*ipinfo.io\",\n \"*ip.anysrc.net\",\n \"*myexternalip.com\",\n \"*myipaddress.com\",\n \"*showipaddress.com\",\n \"*whatismyipaddress.com\",\n \"*wtfismyip.com\",\n \"*ipapi.co\",\n \"*ip-lookup.net\",\n \"*ipstack.com\"\n ) and\n /* Insert noisy false positives here */\n not process.executable :\n (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\WWAHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\smartscreen.exe\",\n \"?:\\\\Windows\\\\System32\\\\MicrosoftEdgeCP.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Fiddler\\\\Fiddler.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Microsoft VS Code\\\\Code.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\OneDrive.exe\"\n )\n", "references": [ "https://community.jisc.ac.uk/blogs/csirt/article/trickbot-analysis-and-mitigation", @@ -37,15 +38,27 @@ "reference": "https://attack.mitre.org/tactics/TA0007/" }, "technique": [ + { + "id": "T1614", + "name": "System Location Discovery", + "reference": "https://attack.mitre.org/techniques/T1614/" + }, { "id": "T1016", "name": "System Network Configuration Discovery", - "reference": "https://attack.mitre.org/techniques/T1016/" + "reference": "https://attack.mitre.org/techniques/T1016/", + "subtechnique": [ + { + "id": "T1016.001", + "name": "Internet Connection Discovery", + "reference": "https://attack.mitre.org/techniques/T1016/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/discovery_privileged_localgroup_membership.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json index effcfa6aef6f8..9772cdf9f1ada 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json @@ -11,7 +11,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Enumeration of Privileged Local Groups Membership", - "note": "## Config\n\nThis will require Windows security event 4799 by enabling audit success for the Windows Account Management category and\nthe Security Group Management subcategory.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n\n", + "note": "## Triage and analysis\n\n### Investigating Enumeration of Privileged Local Groups Membership\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the enumeration of privileged local groups' membership by suspicious processes, and excludes known\nlegitimate utilities and programs installed. Attackers can use this information to decide the next steps of the attack,\nsuch as mapping targets for credential compromise and other post-exploitation activities.\n\n#### Possible investigation steps\n\n- Identify the process, host and user involved on the event.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n- Retrieve the process executable and determine if it is malicious:\n - Check if the file belongs to the operating system or has a valid digital signature.\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Security Group Management' audit policy must be configured (Success).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nAccount Management >\nAudit Security Group Management (Success)\n```\n\nMicrosoft introduced the [event used](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4799) in this detection rule on Windows 10 and Windows Server 2016 or later operating systems.\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "iam where event.action == \"user-member-enumerated\" and\n\n /* noisy and usual legit processes excluded */\n not winlog.event_data.CallerProcessName:\n (\"?:\\\\Windows\\\\System32\\\\VSSVC.exe\",\n \"?:\\\\Windows\\\\System32\\\\SearchIndexer.exe\",\n \"?:\\\\Windows\\\\System32\\\\CompatTelRunner.exe\",\n \"?:\\\\Windows\\\\System32\\\\oobe\\\\msoobe.exe\",\n \"?:\\\\Windows\\\\System32\\\\net1.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\Netplwiz.exe\",\n \"?:\\\\Windows\\\\System32\\\\msiexec.exe\",\n \"?:\\\\Windows\\\\System32\\\\CloudExperienceHostBroker.exe\",\n \"?:\\\\Windows\\\\System32\\\\wbem\\\\WmiPrvSE.exe\",\n \"?:\\\\Windows\\\\System32\\\\SrTasks.exe\",\n \"?:\\\\Windows\\\\System32\\\\lsass.exe\",\n \"?:\\\\Windows\\\\System32\\\\diskshadow.exe\",\n \"?:\\\\Windows\\\\System32\\\\dfsrs.exe\",\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\") and\n /* privileged local groups */\n (group.name:(\"admin*\",\"RemoteDesktopUsers\") or\n winlog.event_data.TargetSid:(\"S-1-5-32-544\",\"S-1-5-32-555\"))\n", "risk_score": 43, "rule_id": "291a0de9-937a-4189-94c0-3e847c8b13e4", @@ -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/discovery_remote_system_discovery_commands_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json index 27f4852fcccc3..3b004c28fe846 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json @@ -2,7 +2,7 @@ "author": [ "Elastic" ], - "description": "Discovery of remote system information using built-in commands, which may be used to mover laterally.", + "description": "Discovery of remote system information using built-in commands, which may be used to move laterally.", "from": "now-9m", "index": [ "logs-endpoint.events.*", @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Remote System Discovery Commands", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Remote System Discovery Commands\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `arp` or `nbstat` utilities to enumerate remote systems in the environment,\nwhich is useful for attackers to identify lateral movement targets.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"nbtstat.exe\" and process.args : (\"-n\", \"-s\")) or\n (process.name : \"arp.exe\" and process.args : \"-a\")\n", "risk_score": 21, "rule_id": "0635c542-1b96-4335-9b47-126582d2c19a", @@ -33,6 +33,11 @@ "reference": "https://attack.mitre.org/tactics/TA0007/" }, "technique": [ + { + "id": "T1016", + "name": "System Network Configuration Discovery", + "reference": "https://attack.mitre.org/techniques/T1016/" + }, { "id": "T1018", "name": "Remote System Discovery", @@ -43,5 +48,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/discovery_security_software_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json index 5049236e3f248..ebf7a44a5fdcc 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Security Software Discovery using WMIC", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Security Software Discovery using WMIC\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `wmic` utility with arguments compatible to the enumeration of the security\nsoftware installed on the host. Attackers can use this information to decide whether or not to infect a system, disable\nprotections, use bypasses, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name:\"wmic.exe\" or process.pe.original_file_name:\"wmic.exe\") and\n process.args:\"/namespace:\\\\\\\\root\\\\SecurityCenter2\" and process.args:\"Get\"\n", "risk_score": 47, "rule_id": "6ea55c81-e2ba-42f2-a134-bccf857ba922", @@ -50,5 +50,5 @@ ], "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/discovery_whoami_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json index 9f9cc2b7a65d8..1ec25bb00b392 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json @@ -10,12 +10,13 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "logs-system.*" ], "language": "eql", "license": "Elastic License v2", "name": "Whoami Process Activity", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Whoami Process Activity\n\nAfter successfully compromising an environment, attackers may try to gain situational awareness to plan their next steps.\nThis can happen by running commands to enumerate network resources, users, connections, files, and installed security\nsoftware.\n\nThis rule looks for the execution of the `whoami` utility. Attackers commonly use this utility to measure their current\nprivileges, discover the current user, determine if a privilege escalation was successful, etc.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed using the account, such as commands executed, files created or modified, and\nnetwork connections.\n\n### False positive analysis\n\n- Discovery activities are not inherently malicious if they occur in isolation. As long as the analyst did not identify\nsuspicious activity related to the user or host, such alerts can be dismissed.\n\n### Related rules\n\n- Account Discovery Command via SYSTEM Account - 2856446a-34e6-435b-9fb5-f8f040bfa7ed\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection via the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and process.name : \"whoami.exe\"\n", "risk_score": 21, "rule_id": "ef862985-3f13-4262-a686-5f357bbb9bc2", @@ -46,5 +47,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/domain_added_to_google_workspace_trusted_domains.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json index 9b703f6108231..26d8afd697d00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/domain_added_to_google_workspace_trusted_domains.json @@ -33,5 +33,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json new file mode 100644 index 0000000000000..b974250b93bc0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of a Process ID (PID), lock or reboot file created in temporary file storage paradigm (tmpfs) directory /var/run. On Linux, the PID files typically hold the process ID to track previous copies running and manage other tasks. Certain Linux malware use the /var/run directory for holding data, executables and other tasks, disguising itself or these files as legitimate PID files.", + "false_positives": [ + "False-Positives (FP) can appear if the PID file is legitimate and holding a process ID as intended. To differentiate, if the PID file is an executable or larger than 10 bytes, it should be ruled suspicious." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Abnormal Process ID or Lock File Created", + "note": "## Triage and analysis\n\n### Investigating Abnormal Process ID or Lock File Created\nDetection alerts from this rule indicate that an unusual PID file was created and could potentially have alternate purposes during an intrusion. Here are some possible avenues of investigation:\n- Run the following in Osquery to quickly identify unsual PID file size: \"SELECT f.size, f.uid, f.type, f.path from file f WHERE path like '/var/run/%pid';\"\n- Examine the history of this file creation and from which process it was created by using the \"lsof\" command.\n- Examine the contents of the PID file itself, simply by running the \"cat\" command to determine if the expected process ID integer exists and if not, the PID file is not legitimate.\n- Examine the reputation of the SHA256 hash from the PID file in a database like VirusTotal to identify additional pivots and artifacts for investigation.", + "query": "/* add file size filters when data is available */\nfile where event.type == \"creation\" and user.id == \"0\" and\n file.path regex~ \"\"\"/var/run/\\w+\\.(pid|lock|reboot)\"\"\" and file.extension in (\"pid\",\"lock\",\"reboot\") and\n\n /* handle common legitimate files */\n\n not file.name in (\n \"auditd.pid\",\n \"python*\",\n \"apport.pid\",\n \"apport.lock\",\n \"kworker*\",\n \"gdm3.pid\",\n \"sshd.pid\",\n \"acpid.pid\",\n \"unattended-upgrades.lock\",\n \"unattended-upgrades.pid\",\n \"cmd.pid\",\n \"cron*.pid\"\n )\n", + "references": [ + "https://www.sandflysecurity.com/blog/linux-file-masquerading-and-malicious-pids-sandfly-1-2-6-update/", + "https://twitter.com/GossiTheDog/status/1522964028284411907", + "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf" + ], + "risk_score": 43, + "rule_id": "cac91072-d165-11ec-a764-f661ea17fbce", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Execution", + "BPFDoor" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1106", + "name": "Native API", + "reference": "https://attack.mitre.org/techniques/T1106/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_binary.json deleted file mode 100644 index c9b900ce92a56..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_binary.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary apt/apt-get abuse to breakout out of restricted shells or environments by spawning an interactive system shell. The apt utility allows us to manage installation and removal of softwares on Debian based Linux distributions and the activity of spawning shell is not a standard use of this binary for a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via apt/apt-get Changelog Escape", - "query": "process where event.type == \"start\" and process.name == \"sensible-pager\" and\n process.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"sh\", \"bash\", \"dash\") and\n process.parent.name in (\"apt\", \"apt-get\") and process.parent.args == \"changelog\"\n", - "references": [ - "https://gtfobins.github.io/gtfobins/apt/", - "https://gtfobins.github.io/gtfobins/apt-get/" - ], - "risk_score": 47, - "rule_id": "8fed8450-847e-43bd-874c-3bbf0cd425f3", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json index 8b25f73c239bb..418885c9d63f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json @@ -19,7 +19,7 @@ "query": "process where event.type in (\"start\", \"process_started\") and process.name: (\"cmd.exe\", \"powershell.exe\") and\nprocess.parent.name: (\n \"ConfigurationWizard*.exe\",\n \"NetflowDatabaseMaintenance*.exe\",\n \"NetFlowService*.exe\",\n \"SolarWinds.Administration*.exe\",\n \"SolarWinds.Collector.Service*.exe\",\n \"SolarwindsDiagnostics*.exe\"\n )\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html", - "https://github.com/fireeye/sunburst_countermeasures/blob/main/rules/SUNBURST/hxioc/SOLARWINDS%20SUSPICIOUS%20FILEWRITES%20(METHODOLOGY).ioc" + "https://github.com/mandiant/sunburst_countermeasures/blob/main/rules/SUNBURST/hxioc/SUNBURST%20SUSPICIOUS%20FILEWRITES%20(METHODOLOGY).ioc" ], "risk_score": 47, "rule_id": "d72e33fc-6e91-42ff-ac8b-e573268c5a87", @@ -72,5 +72,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/execution_apt_solarwinds_backdoor_unusual_child_processes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json index 12ee31cecda30..0cd9d99630b47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json @@ -19,7 +19,7 @@ "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name: (\"SolarWinds.BusinessLayerHost.exe\", \"SolarWinds.BusinessLayerHostx64.exe\") and\n not process.name : (\n \"APMServiceControl*.exe\",\n \"ExportToPDFCmd*.Exe\",\n \"SolarWinds.Credentials.Orion.WebApi*.exe\",\n \"SolarWinds.Orion.Topology.Calculator*.exe\",\n \"Database-Maint.exe\",\n \"SolarWinds.Orion.ApiPoller.Service.exe\",\n \"WerFault.exe\",\n \"WerMgr.exe\")\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html", - "https://github.com/fireeye/sunburst_countermeasures/blob/main/rules/SUNBURST/hxioc/SOLARWINDS%20SUSPICIOUS%20CHILD%20PROCESSES%20(METHODOLOGY).ioc" + "https://github.com/mandiant/sunburst_countermeasures/blob/main/rules/SUNBURST/hxioc/SUNBURST%20SUSPICIOUS%20CHILD%20PROCESSES%20(METHODOLOGY).ioc" ], "risk_score": 47, "rule_id": "93b22c0a-06a0-4131-b830-b10d5e166ff4", @@ -72,5 +72,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/execution_awk_binary_shell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_awk_binary_shell.json deleted file mode 100644 index 2bf7b9e9271be..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_awk_binary_shell.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary awk abuse to breakout out of restricted shells or environments by spawning an interactive system shell. The awk utility is a text processing language used for data extraction and reporting tools and the activity of spawning shell is not a standard use of this binary for a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via awk Commands", - "query": "process where event.type == \"start\" and process.name in (\"sh\", \"bash\", \"dash\") and\n process.parent.name in (\"nawk\", \"mawk\", \"awk\", \"gawk\") and process.parent.args : \"BEGIN {system(*)}\"\n", - "references": [ - "https://gtfobins.github.io/gtfobins/nawk/", - "https://gtfobins.github.io/gtfobins/mawk/" - ], - "risk_score": 47, - "rule_id": "10754992-28c7-4472-be5b-f3770fd04f2d", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_busybox_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_busybox_binary.json deleted file mode 100644 index daf980004d5e6..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_busybox_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary busybox abuse to break out from restricted environments by spawning an interactive system shell.The busybox is software utility suite that provides several Unix utilities in a single executable file and the activity of spawing a shell is not a standard use of this binary by a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via busybox Shell Evasion", - "query": "process where event.type == \"start\" and process.name == \"busybox\" and process.args_count == 2 and process.args in (\"/bin/sh\", \"/bin/ash\", \"sh\", \"ash\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/busybox/" - ], - "risk_score": 47, - "rule_id": "e9b4a3c7-24fc-49fd-a00f-9c938031eef1", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_c89_c99_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_c89_c99_binary.json deleted file mode 100644 index 1cf6c650b0fe7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_c89_c99_binary.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary c89/c99 abuse to break out from restricted environments by spawning an interactive system shell.The c89/c99 utility is an interface to the standard C compilation system and the activity of spawing a shell is not a standard use of this binary by a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via c89/c99 Shell evasion", - "query": "process where event.type == \"start\" and process.name in (\"sh\", \"dash\", \"bash\") and\n process.parent.name in (\"c89\",\"c99\") and process.parent.args == \"-wrapper\" and\n process.parent.args in (\"sh,-s\", \"bash,-s\", \"dash,-s\", \"/bin/sh,-s\", \"/bin/bash,-s\", \"/bin/dash,-s\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/c89/", - "https://gtfobins.github.io/gtfobins/c99/" - ], - "risk_score": 47, - "rule_id": "1859ce38-6a50-422b-a5e8-636e231ea0cd", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json index 58aa6445e4c19..08e3c55610148 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json @@ -45,5 +45,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_cpulimit_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_cpulimit_binary.json deleted file mode 100644 index 67d3fda3b3889..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_cpulimit_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary cpulimit abuse to break out from restricted environments by spawning an interactive system shell. The cpulimit utility is used to restrict the CPU usage of a process in cases of CPU or system load exceeding the defined threshold and the activity of spawning a shell is not a standard use of this binary by a user or system administrator. This can potentially indicate a malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via cpulimit Shell Evasion", - "query": "process where event.type == \"start\" and process.name in (\"bash\", \"sh\", \"dash\") and\n process.parent.name == \"cpulimit\" and process.parent.args == \"-f\" and\n process.parent.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"sh\", \"bash\", \"dash\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/cpulimit/" - ], - "risk_score": 47, - "rule_id": "0968cfbd-40f0-4b1c-b7b1-a60736c7b241", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_crash_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_crash_binary.json deleted file mode 100644 index a82db8b462fdd..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_crash_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary crash abuse to break out from restricted environments by spawning an interactive system shell.The crash utility helps to analyze Linux crash dump data or a live system and the activity of spawing a shell is not a standard use of this binary by a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via crash Shell evasion", - "query": "process where event.type == \"start\" and process.parent.name == \"crash\" and process.parent.args == \"-h\" and process.name == \"sh\"\n", - "references": [ - "https://gtfobins.github.io/gtfobins/crash/" - ], - "risk_score": 47, - "rule_id": "ee619805-54d7-4c56-ba6f-7717282ddd73", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_env_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_env_binary.json deleted file mode 100644 index c002869ba4c12..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_env_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary env abuse to break out from restricted environments by spawning an interactive system shell.The env utility is a shell command for Unix like OS which is used to print a list of environment variables and the activity of spawning shell is not a standard use of this binary for a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via env Shell Evasion", - "query": "process where event.type == \"start\" and process.name : \"env\" and process.args_count == 2 and process.args : (\"/bin/sh\", \"/bin/bash\", \"sh\", \"bash\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/env/" - ], - "risk_score": 47, - "rule_id": "72d33577-f155-457d-aad3-379f9b750c97", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_expect_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_expect_binary.json deleted file mode 100644 index 2879a9407c920..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_expect_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary expect abuse to break out from restricted environments by spawning an interactive system shell. The expect utility allows us to automate control of interactive applications such as telnet,ftp,ssh and others and the activity of spawning shell is not a standard use of this binary for a user or system administrator and could potentially indicate malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via the expect command", - "query": "process where event.type == \"start\" and process.name in (\"bash\", \"sh\", \"dash\") and\n process.parent.name == \"expect\" and process.parent.args == \"-c\" and\n process.parent.args in (\"spawn /bin/sh;interact\", \"spawn /bin/bash;interact\", \"spawn /bin/dash;interact\", \"spawn sh;interact\", \"spawn bash;interact\", \"spawn dash;interact\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/expect/" - ], - "risk_score": 47, - "rule_id": "fd3fc25e-7c7c-4613-8209-97942ac609f6", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_find_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_find_binary.json deleted file mode 100644 index a6700e8449911..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_find_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary find abuse to break out from restricted environments by spawning an interactive system shell. The find command in Unix is a command line utility for walking a file hirerarchy and the activity of spawning shell is not a standard use of this binary for a user or system administrator.It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via the find command", - "query": "process where event.type == \"start\" and process.name in (\"bash\", \"sh\") and\n process.parent.name == \"find\" and process.parent.args == \"-exec\" and\n process.parent.args == \";\" and process.parent.args in (\"/bin/bash\", \"/bin/sh\", \"bash\", \"sh\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/find/" - ], - "risk_score": 47, - "rule_id": "6f683345-bb10-47a7-86a7-71e9c24fb358", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_flock_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_flock_binary.json deleted file mode 100644 index 13d16968697f0..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_flock_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary flock abuse to break out from restricted environments by spawning an interactive system shell.The flock utility allows us to manage advisory file locks in shell scripts or on the command line and the activity of spawing a shell is not a standard use of this binary by a user or system administrator. It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via flock Shell evasion", - "query": "process where event.type == \"start\" and process.parent.name == \"flock\" and process.parent.args == \"-u\" and process.parent.args == \"/\" and process.parent.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"sh\", \"bash\", \"dash\") and process.name in (\"bash\", \"dash\", \"sh\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/flock/" - ], - "risk_score": 47, - "rule_id": "f52362cd-baf1-4b6d-84be-064efc826461", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_gcc_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_gcc_binary.json deleted file mode 100644 index e5b93a08b0ef9..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_gcc_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary gcc abuse to break out from restricted environments by spawning an interactive system shell.The gcc utility is a complier system for various languages and mainly used to complie C and C++ programs and the activity of spawning shell is not a standard use of this binary for a user or system administrator.It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via the gcc command", - "query": "process where event.type == \"start\" and process.name in (\"sh\", \"dash\", \"bash\") and\n process.parent.name == \"gcc\" and process.parent.args == \"-wrapper\" and\n process.parent.args in (\"sh,-s\", \"bash,-s\", \"dash,-s\", \"/bin/sh,-s\", \"/bin/bash,-s\", \"/bin/dash,-s\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/gcc/" - ], - "risk_score": 47, - "rule_id": "da986d2c-ffbf-4fd6-af96-a88dbf68f386", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json index 00476e08dd4c1..3108c9c02f170 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json @@ -13,6 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Execution of File Written or Modified by Microsoft Office", + "note": "## Triage and analysis\n\n### Investigating Execution of File Written or Modified by Microsoft Office\n\nMicrosoft Office is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently\ntargeted for initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThis rule searches for executable files written by MS Office applications executed in sequence. This is most likely the result\nof the execution of malicious documents or exploitation for initial access or privilege escalation. This rule can also detect\nsuspicious processes masquerading as the MS Office applications.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector. \n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence with maxspan=2h\n [file where event.type != \"deletion\" and file.extension : \"exe\" and\n (process.name : \"WINWORD.EXE\" or\n process.name : \"EXCEL.EXE\" or\n process.name : \"OUTLOOK.EXE\" or\n process.name : \"POWERPNT.EXE\" or\n process.name : \"eqnedt32.exe\" or\n process.name : \"fltldr.exe\" or\n process.name : \"MSPUB.EXE\" or\n process.name : \"MSACCESS.EXE\")\n ] by host.id, file.path\n [process where event.type in (\"start\", \"process_started\")] by host.id, process.executable\n", "risk_score": 21, "rule_id": "0d8ad79f-9025-45d8-80c1-4f0cd3c5e8e5", @@ -63,5 +64,5 @@ } ], "type": "eql", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mysql_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mysql_binary.json deleted file mode 100644 index 75bed04b4b54f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_mysql_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies MySQL server abuse to break out from restricted environments by spawning an interactive system shell.The MySQL is an open source relational database management system and the activity of spawning shell is not a standard use of this binary for a user or system administrator.It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via the mysql command", - "query": "process where event.type == \"start\" and process.name in (\"bash\", \"sh\", \"dash\") and\n process.parent.name == \"mysql\" and process.parent.args == \"-e\" and\n process.parent.args : (\"\\\\!*sh\", \"\\\\!*bash\", \"\\\\!*dash\", \"\\\\!*/bin/sh\", \"\\\\!*/bin/bash\", \"\\\\!*/bin/dash\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/mysql/" - ], - "risk_score": 47, - "rule_id": "83b2c6e5-e0b2-42d7-8542-8f3af86a1acb", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json index 42fcf07c75a90..a74fb9e92765f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json @@ -13,8 +13,9 @@ "language": "eql", "license": "Elastic License v2", "name": "Execution of File Written or Modified by PDF Reader", + "note": "## Triage and analysis\n\n### Investigating Execution of File Written or Modified by PDF Reader\n\nPDF is a common file type used in corporate environments and most machines have software to \nhandle these files. This creates a vector where attackers can exploit the engines and technology behind this class of\nsoftware for initial access or privilege escalation.\n\nThis rule searches for executable files written by PDF reader software and executed in sequence. This is most likely the\nresult of exploitation for privilege escalation or initial access. This rule can also detect suspicious processes masquerading as\nPDF readers.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the PDF documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector. \n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence with maxspan=2h\n [file where event.type != \"deletion\" and file.extension : \"exe\" and\n (process.name : \"AcroRd32.exe\" or\n process.name : \"rdrcef.exe\" or\n process.name : \"FoxitPhantomPDF.exe\" or\n process.name : \"FoxitReader.exe\") and\n not (file.name : \"FoxitPhantomPDF.exe\" or\n file.name : \"FoxitPhantomPDFUpdater.exe\" or\n file.name : \"FoxitReader.exe\" or\n file.name : \"FoxitReaderUpdater.exe\" or\n file.name : \"AcroRd32.exe\" or\n file.name : \"rdrcef.exe\")\n ] by host.id, file.path\n [process where event.type in (\"start\", \"process_started\")] by host.id, process.executable\n", - "risk_score": 21, + "risk_score": 73, "rule_id": "1defdd62-cd8d-426e-a246-81a37751bb2b", "severity": "high", "tags": [ @@ -63,5 +64,5 @@ } ], "type": "eql", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json index 8f26482c048ed..4d43c1c64ee60 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Suspicious Portable Executable Encoded in Powershell Script", - "note": "## Triage and analysis.\n\n### Investigating Suspicious Portable Executable Encoded in Powershell Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell in-memory capabilities to inject executables into memory without touching the disk,\nbypassing file-based security protections. These executables are generally base64 encoded.\n\n#### Possible investigation steps\n\n- Examine script content that triggered the detection. \n- Investigate the script execution chain (parent process tree).\n- Inspect any file or network events from the suspicious PowerShell host process instance.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n- Retrieve the script and execute it in a sandbox or controlled environment.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious Portable Executable Encoded in Powershell Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can abuse PowerShell in-memory capabilities to inject executables into memory without touching the disk,\nbypassing file-based security protections. These executables are generally base64 encoded.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell PSReflect Script - 56f2e9b5-4803-4e44-a0a4-a52dc79d57fe\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Reimage the host operating system or restore the compromised files to clean versions.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text : (\n TVqQAAMAAAAEAAAA\n )\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0109_windows_powershell_script_block_log.md" @@ -52,5 +52,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json index d6fc7d1ff1553..336cb17c67a96 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "PowerShell PSReflect Script", - "note": "## Triage and analysis\n\n### Investigating PowerShell PSReflect Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nPSReflect is a library that enables PowerShell to access win32 API functions in an uncomplicated way. It also helps to\ncreate enums and structs easily\u2014all without touching the disk.\n\nAlthough this is an interesting project for every developer and admin out there, it is mainly used in the red team and\nmalware tooling for its capabilities.\n\nDetecting the core implementation of PSReflect means detecting most of the tooling that uses Windows API through\nPowerShell, enabling defenders to discover tools being dropped in the environment.\n\n#### Possible investigation steps\n\n- Check for additional PowerShell and command-line logs that indicate that imported functions were run.\n- Gather the script content that may be split into multiple script blocks (the field `powershell.file.script_block_id`\ncan be used for filtering), and identify its capabilities.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Consider whether the user needs PowerShell to complete its tasks.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- PowerShell Reflection Assembly Load - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Configure AppLocker or equivalent software to restrict access to PowerShell for regular users.\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", + "note": "## Triage and analysis\n\n### Investigating PowerShell PSReflect Script\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nPSReflect is a library that enables PowerShell to access win32 API functions in an uncomplicated way. It also helps to\ncreate enums and structs easily\u2014all without touching the disk.\n\nAlthough this is an interesting project for every developer and admin out there, it is mainly used in the red team and\nmalware tooling for its capabilities.\n\nDetecting the core implementation of PSReflect means detecting most of the tooling that uses Windows API through\nPowerShell, enabling defenders to discover tools being dropped in the environment.\n\n#### Possible investigation steps\n\n- Examine the script content that triggered the detection; look for suspicious DLL imports, collection or exfiltration\ncapabilities, suspicious functions, encoded or compressed data, and other potentially malicious characteristics. The\nscript content that may be split into multiple script blocks (you can use the field `powershell.file.script_block_id`\nfor filtering).\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Check for additional PowerShell and command-line logs that indicate that imported functions were run.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Evaluate whether the user needs to use PowerShell to complete tasks.\n- Retrieve the script and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be configured (Enable).\n\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", "query": "event.category:process and \n powershell.file.script_block_text:(\n \"New-InMemoryModule\" or\n \"Add-Win32Type\" or\n psenum or\n DefineDynamicAssembly or\n DefineDynamicModule or\n \"Reflection.TypeAttributes\" or\n \"Reflection.Emit.OpCodes\" or\n \"Reflection.Emit.CustomAttributeBuilder\" or\n \"Runtime.InteropServices.DllImportAttribute\"\n )\n", "references": [ "https://github.com/mattifestation/PSReflect/blob/master/PSReflect.psm1", @@ -61,5 +61,5 @@ ], "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/execution_process_started_from_process_id_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_from_process_id_file.json new file mode 100644 index 0000000000000..bd26965a489a6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_from_process_id_file.json @@ -0,0 +1,54 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a new process starting from a process ID (PID), lock or reboot file within the temporary file storage paradigm (tmpfs) directory /var/run directory. On Linux, the PID files typically hold the process ID to track previous copies running and manage other tasks. Certain Linux malware use the /var/run directory for holding data, executables and other tasks, disguising itself or these files as legitimate PID files.", + "false_positives": [ + "False-Positives (FP) should be at a minimum with this detection as PID files are meant to hold process IDs, not inherently be executables that spawn processes." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Process Started from Process ID (PID) File", + "note": "## Triage and analysis\n\n### Investigating Process Started from Process ID (PID) File\nDetection alerts from this rule indicate a process spawned from an executable masqueraded as a legitimate PID file which is very unusual and should not occur. Here are some possible avenues of investigation:\n- Examine parent and child process relationships of the new process to determine if other processes are running.\n- Examine the /var/run directory using Osquery to determine other potential PID files with unsually large file sizes, indicative of it being an executable: \"SELECT f.size, f.uid, f.type, f.path from file f WHERE path like '/var/run/%%';\"\n- Examine the reputation of the SHA256 hash from the PID file in a database like VirusTotal to identify additional pivots and artifacts for investigation.", + "query": "process where event.type == \"start\" and user.id == \"0\" and process.executable regex~ \"\"\"/var/run/\\w+\\.(pid|lock|reboot)\"\"\"\n", + "references": [ + "https://www.sandflysecurity.com/blog/linux-file-masquerading-and-malicious-pids-sandfly-1-2-6-update/", + "https://twitter.com/GossiTheDog/status/1522964028284411907", + "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf" + ], + "risk_score": 73, + "rule_id": "3688577a-d196-11ec-90b0-f661ea17fbce", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Execution", + "BPFDoor" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_in_shared_memory_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_in_shared_memory_directory.json new file mode 100644 index 0000000000000..18931d56e6e76 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_in_shared_memory_directory.json @@ -0,0 +1,52 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of a binary by root in Linux shared memory directories: (/dev/shm/, /run/shm/, /var/run/, /var/lock/). This activity is to be considered highly abnormal and should be investigated. Threat actors have placed executables used for persistence on high-uptime servers in these directories as system backdoors.", + "false_positives": [ + "Directories /dev/shm and /run/shm are temporary file storage directories in Linux. They are intended to appear as a mounted file system, but uses virtual memory instead of a persistent storage device and thus are used for mounting file systems in legitimate purposes." + ], + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Binary Executed from Shared Memory Directory", + "query": "process where event.type == \"start\" and \n event.action == \"exec\" and user.name == \"root\" and \n process.executable : (\n \"/dev/shm/*\",\n \"/run/shm/*\",\n \"/var/run/*\",\n \"/var/lock/*\"\n )\n", + "references": [ + "https://linuxsecurity.com/features/fileless-malware-on-linux", + "https://twitter.com/GossiTheDog/status/1522964028284411907" + ], + "risk_score": 73, + "rule_id": "3f3f9fe2-d095-11ec-95dc-f661ea17fbce", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Execution", + "BPFDoor" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json index 523dda198e2d8..2fb8886ffb543 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json @@ -43,5 +43,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "query", - "version": 7 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_evasion_linux_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_evasion_linux_binary.json new file mode 100644 index 0000000000000..1715625e020b1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_evasion_linux_binary.json @@ -0,0 +1,80 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies Linux binary(s) abuse to breakout of restricted shells or environments by spawning an interactive system shell. The linux utility(s) activity of spawning shell is not a standard use of the binary for a user or system administrator. It may indicates an attempt to improve the capabilities or stability of an adversary access.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Linux Restricted Shell Breakout via Linux Binary(s)", + "note": "## Triage and analysis\n\n### Investigating Shell Evasion via Linux Utilities\nDetection alerts from this rule indicate that a Linux utility has been abused to breakout of restricted shells or\nenvironments by spawning an interactive system shell.\nHere are some possible avenues of investigation:\n- Examine the entry point to the host and user in action via the Analyse View.\n - Identify the session entry leader and session user\n- Examine the contents of session leading to the abuse via the Session View.\n - Examine the command execution pattern in the session, which may lead to suspricous activities\n- Examine the execution of commands in the spawned shell.\n - Identify imment threat to the system from the executed commands\n - Take necessary incident response actions to contain any malicious behviour caused via this execution.\n\n### Related rules\n\n- A malicious spawned shell can execute any of the possible MITTRE ATT&CK vectors mainly to impair defences.\n- Hence its adviced to enable defence evasion and privilige escalation rules accordingly in your environment\n\n### Response and remediation\n\nInitiate the incident response process based on the outcome of the triage.\n\n- If the triage releaved suspicious netwrok activity from the malicious spawned shell,\n - Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware execution via the maliciously spawned shell,\n - Search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- If the triage revelaed defence evasion for imparing defenses\n - Isolate the involved host to prevent further post-compromise behavior.\n - Identified the disabled security guard components on the host and take necessary steps in renebaling the same.\n - If any tools have been disbaled / uninstalled or config tampered work towards reenabling the same.\n- If the triage revelaed addition of persistence mechanism exploit like auto start scripts\n - Isolate further login to the systems that can initae auto start scripts.\n - Identify the auto start scripts and disable and remove the same from the systems\n- If the triage revealed data crawling or data export via remote copy\n - Investigate credential exposure on systems compromised / used / decoded by the attacker during the data crawling\n - Intiate compromised credential deactivation and credential rotation process for all exposed crednetials.\n - Investiagte if any IPR data was accessed during the data crawling and take appropriate actions.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe session view analysis for the command alerted is avalible in versions 8.2 and above.\n", + "query": "process where event.type == \"start\" and\n\n /* launch shells from unusual process */\n (process.name == \"capsh\" and process.args == \"--\") or\n\n /* launching shells from unusual parents or parent+arg combos */\n (process.name in (\"bash\", \"sh\", \"dash\",\"ash\") and\n (process.parent.name in (\"byebug\",\"git\",\"ftp\")) or\n\n /* shells specified in parent args */\n /* nice rule is broken in 8.2 */\n (process.parent.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"/bin/ash\", \"sh\", \"bash\", \"dash\", \"ash\") and\n (process.parent.name == \"nice\") or\n (process.parent.name == \"cpulimit\" and process.parent.args == \"-f\") or\n (process.parent.name == \"find\" and process.parent.args == \"-exec\" and process.parent.args == \";\") or\n (process.parent.name == \"flock\" and process.parent.args == \"-u\" and process.parent.args == \"/\")\n ) or\n\n /* shells specified in args */\n (process.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"/bin/ash\", \"sh\", \"bash\", \"dash\", \"ash\") and\n (process.parent.name == \"crash\" and process.parent.args == \"-h\") or\n (process.name == \"sensible-pager\" and process.parent.name in (\"apt\", \"apt-get\") and process.parent.args == \"changelog\")\n /* scope to include more sensible-pager invoked shells with different parent process to reduce noise and remove false positives */\n )\n ) or\n (process.name == \"busybox\" and process.args_count == 2 and process.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"/bin/ash\", \"sh\", \"bash\", \"dash\", \"ash\") )or\n (process.name == \"env\" and process.args_count == 2 and process.args in (\"/bin/sh\", \"/bin/bash\", \"/bin/dash\", \"/bin/ash\", \"sh\", \"bash\", \"dash\", \"ash\")) or\n (process.parent.name in (\"vi\", \"vim\") and process.parent.args == \"-c\" and process.parent.args in (\":!/bin/bash\", \":!/bin/sh\", \":!bash\", \":!sh\")) or\n (process.parent.name in (\"c89\",\"c99\", \"gcc\") and process.parent.args in (\"sh,-s\", \"bash,-s\", \"dash,-s\", \"ash,-s\", \"/bin/sh,-s\", \"/bin/bash,-s\", \"/bin/dash,-s\", \"/bin/ash,-s\") and process.parent.args == \"-wrapper\") or\n (process.parent.name == \"expect\" and process.parent.args == \"-c\" and process.parent.args in (\"spawn /bin/sh;interact\", \"spawn /bin/bash;interact\", \"spawn /bin/dash;interact\", \"spawn sh;interact\", \"spawn bash;interact\", \"spawn dash;interact\")) or\n (process.parent.name == \"mysql\" and process.parent.args == \"-e\" and process.parent.args in (\"\\\\!*sh\", \"\\\\!*bash\", \"\\\\!*dash\", \"\\\\!*/bin/sh\", \"\\\\!*/bin/bash\", \"\\\\!*/bin/dash\")) or\n (process.parent.name == \"ssh\" and process.parent.args == \"-o\" and process.parent.args in (\"ProxyCommand=;sh 0<&2 1>&2\", \"ProxyCommand=;bash 0<&2 1>&2\", \"ProxyCommand=;dash 0<&2 1>&2\", \"ProxyCommand=;/bin/sh 0<&2 1>&2\", \"ProxyCommand=;/bin/bash 0<&2 1>&2\", \"ProxyCommand=;/bin/dash 0<&2 1>&2\")) or\n (process.parent.name in (\"nawk\", \"mawk\", \"awk\", \"gawk\") and process.parent.args : \"BEGIN {system(*)}\")\n", + "references": [ + "https://gtfobins.github.io/gtfobins/apt/", + "https://gtfobins.github.io/gtfobins/apt-get/", + "https://gtfobins.github.io/gtfobins/nawk/", + "https://gtfobins.github.io/gtfobins/mawk/", + "https://gtfobins.github.io/gtfobins/awk/", + "https://gtfobins.github.io/gtfobins/gawk/", + "https://gtfobins.github.io/gtfobins/busybox/", + "https://gtfobins.github.io/gtfobins/c89/", + "https://gtfobins.github.io/gtfobins/c99/", + "https://gtfobins.github.io/gtfobins/cpulimit/", + "https://gtfobins.github.io/gtfobins/crash/", + "https://gtfobins.github.io/gtfobins/env/", + "https://gtfobins.github.io/gtfobins/expect/", + "https://gtfobins.github.io/gtfobins/find/", + "https://gtfobins.github.io/gtfobins/flock/", + "https://gtfobins.github.io/gtfobins/gcc/", + "https://gtfobins.github.io/gtfobins/mysql/", + "https://gtfobins.github.io/gtfobins/nice/", + "https://gtfobins.github.io/gtfobins/ssh/", + "https://gtfobins.github.io/gtfobins/vi/", + "https://gtfobins.github.io/gtfobins/vim/", + "https://gtfobins.github.io/gtfobins/capsh/", + "https://gtfobins.github.io/gtfobins/byebug/", + "https://gtfobins.github.io/gtfobins/git/", + "https://gtfobins.github.io/gtfobins/ftp/" + ], + "risk_score": 47, + "rule_id": "52376a86-ee86-4967-97ae-1a05f55816f0", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Execution", + "GTFOBins" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.004", + "name": "Unix Shell", + "reference": "https://attack.mitre.org/techniques/T1059/004/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ssh_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ssh_binary.json deleted file mode 100644 index 197ea320339f9..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ssh_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary ssh abuse to break out from restricted environments by spawning an interactive system shell.The ssh is a network protocol that gives users,particularly system administrators a secure way to access a computer over a network and the activity of spawning shell is not a standard use of this binary for a user or system administrator.It indicates a potentially malicious actor attempting to improve the capabilities or stability of their access.", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via the ssh command", - "query": "process where event.type == \"start\" and process.name : (\"bash\", \"sh\", \"dash\") and\n process.parent.name == \"ssh\" and process.parent.args == \"-o\" and\n process.parent.args in (\"ProxyCommand=;sh 0<&2 1>&2\", \"ProxyCommand=;bash 0<&2 1>&2\", \"ProxyCommand=;dash 0<&2 1>&2\", \"ProxyCommand=;/bin/sh 0<&2 1>&2\", \"ProxyCommand=;/bin/bash 0<&2 1>&2\", \"ProxyCommand=;/bin/dash 0<&2 1>&2\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/ssh/" - ], - "risk_score": 47, - "rule_id": "97da359b-2b61-4a40-b2e4-8fc48cf7a294", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json index 3897f78345580..3264382e5476c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious PDF Reader Child Process", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious PDF Reader Child Process\n\nPDF is a common file type used in corporate environments and most machines have software to handle these files. This\ncreates a vector where attackers can exploit the engines and technology behind this class of software for initial access\nor privilege escalation.\n\nThis rule looks for commonly abused built-in utilities spawned by a PDF reader process, which is likely a malicious behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve PDF documents received and opened by the user that could cause this behavior. Common locations include, but\nare not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector. \n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : (\"AcroRd32.exe\",\n \"Acrobat.exe\",\n \"FoxitPhantomPDF.exe\",\n \"FoxitReader.exe\") and\n process.name : (\"arp.exe\", \"dsquery.exe\", \"dsget.exe\", \"gpresult.exe\", \"hostname.exe\", \"ipconfig.exe\", \"nbtstat.exe\",\n \"net.exe\", \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"ping.exe\", \"qprocess.exe\",\n \"quser.exe\", \"qwinsta.exe\", \"reg.exe\", \"sc.exe\", \"systeminfo.exe\", \"tasklist.exe\", \"tracert.exe\",\n \"whoami.exe\", \"bginfo.exe\", \"cdb.exe\", \"cmstp.exe\", \"csi.exe\", \"dnx.exe\", \"fsi.exe\", \"ieexec.exe\",\n \"iexpress.exe\", \"installutil.exe\", \"Microsoft.Workflow.Compiler.exe\", \"msbuild.exe\", \"mshta.exe\",\n \"msxsl.exe\", \"odbcconf.exe\", \"rcsi.exe\", \"regsvr32.exe\", \"xwizard.exe\", \"atbroker.exe\",\n \"forfiles.exe\", \"schtasks.exe\", \"regasm.exe\", \"regsvcs.exe\", \"cmd.exe\", \"cscript.exe\",\n \"powershell.exe\", \"pwsh.exe\", \"wmic.exe\", \"wscript.exe\", \"bitsadmin.exe\", \"certutil.exe\", \"ftp.exe\")\n", "risk_score": 21, "rule_id": "53a26770-9cbd-40c5-8b57-61d01a325e14", @@ -43,5 +43,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/execution_suspicious_powershell_imgload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json index d9c90946c91fa..bda8c28d7e982 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious PowerShell Engine ImageLoad", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious PowerShell Engine ImageLoad\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell without having to execute `PowerShell.exe` directly. This technique, often called\n\"PowerShell without PowerShell,\" works by using the underlying System.Management.Automation namespace and can bypass\napplication allowlisting and PowerShell security features.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Retrieve the implementation (DLL, executable, etc.) and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Some vendors have their own PowerShell implementations that are shipped with\nsome products. These benign true positives (B-TPs) can be added as exceptions if necessary after analysis.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "library where dll.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") and\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\") and\nnot process.executable regex~ \"\"\"C:\\\\Program Files( \\(x86\\))?\\\\*\\.exe\"\"\" and\n not process.name :\n (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", "risk_score": 47, "rule_id": "852c1f19-68e8-43a6-9dce-340771fe1be3", @@ -50,5 +50,5 @@ ], "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/execution_vi_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_vi_binary.json deleted file mode 100644 index ef407b26aa5d7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_vi_binary.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "author": [ - "Elastic" - ], - "description": "Identifies Linux binary find abuse to break out from restricted environments by spawning an interactive system shell. The vi/vim is the standard text editor in Linux distribution and the activity of spawning a shell is not a standard use of this binary by a user or system administrator and could potentially indicate malicious actor attempting to improve the capabilities or stability of their access.\"", - "from": "now-9m", - "index": [ - "logs-endpoint.events.*" - ], - "language": "eql", - "license": "Elastic License v2", - "name": "Linux Restricted Shell Breakout via the vi command", - "query": "process where event.type == \"start\" and process.parent.name in (\"vi\", \"vim\") and process.parent.args == \"-c\" and process.parent.args in (\":!/bin/bash\", \":!/bin/sh\", \":!bash\", \":!sh\") and process.name in (\"bash\", \"sh\")\n", - "references": [ - "https://gtfobins.github.io/gtfobins/vi/" - ], - "risk_score": 47, - "rule_id": "89583d1b-3c2e-4606-8b74-0a9fd2248e88", - "severity": "medium", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "Execution", - "GTFOBins" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/", - "subtechnique": [ - { - "id": "T1059.004", - "name": "Unix Shell", - "reference": "https://attack.mitre.org/techniques/T1059/004/" - } - ] - } - ] - } - ], - "timestamp_override": "event.ingested", - "type": "eql", - "version": 1 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json index e66959acd741e..3614ec7bcaf72 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Conhost Spawned By Suspicious Parent Process", - "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Conhost Spawned By Suspicious Parent Process\n\nThe Windows Console Host, or `conhost.exe`, is both the server application for all of the Windows Console APIs as well as\nthe classic Windows user interface for working with command-line applications.\n\nAttackers often rely on custom shell implementations to avoid using built-in command interpreters like `cmd.exe` and \n`PowerShell.exe` and bypass application allowlisting and security features. Attackers commonly inject these implementations into\nlegitimate system processes.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behaviors in the alert timeframe.\n- Retrieve the parent process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious Process from Conhost - 28896382-7d4f-4d50-9b72-67091901fd26\n- Suspicious PowerShell Engine ImageLoad - 852c1f19-68e8-43a6-9dce-340771fe1be3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : \"conhost.exe\" and\n process.parent.name : (\"svchost.exe\", \"lsass.exe\", \"services.exe\", \"smss.exe\", \"winlogon.exe\", \"explorer.exe\",\n \"dllhost.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"userinit.exe\", \"wininit.exe\", \"spoolsv.exe\",\n \"wermgr.exe\", \"csrss.exe\", \"ctfmon.exe\")\n", "references": [ "https://www.fireeye.com/blog/threat-research/2017/08/monitoring-windows-console-activity-part-one.html" @@ -46,5 +46,5 @@ ], "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/google_workspace_admin_role_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json index cdb5051feb498..26170532f903e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_admin_role_deletion.json @@ -33,5 +33,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json index 8c4ba919753ae..f4cd48f2f7e86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_mfa_enforcement_disabled.json @@ -33,5 +33,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 11 + "version": 13 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json index 3a18be0ae60c8..3876ce549fd6d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/google_workspace_policy_modified.json @@ -30,5 +30,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 11 + "version": 13 } 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 index 13864eaf8b4ec..e86276e86052d 100644 --- 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 @@ -15,7 +15,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Third-party Backup Files Deleted via Unexpected Process", - "note": "## Triage and analysis\n\n### Investigating Third-party Backup Files Deleted via Unexpected Process\n\nBackups are a significant obstacle for any ransomware operation. They allow the victim to resume business by performing\ndata recovery, making them a valuable target.\n\nAttackers can delete backups from the host and gain access to backup servers to remove centralized backups for the\nenvironment, ensuring that victims have no alternatives to paying the ransom.\n\nThis rule identifies file deletions performed by a process that does not belong to the backup suite and aims to delete\nVeritas or Veeam backups.\n\n#### Possible investigation steps\n\n- Identify the process (location, name, etc.) and the user that performed this operation.\n- Check whether the account is authorized to perform this operation.\n- Confirm whether the account owner is aware of the operation.\n- Investigate other alerts associated with the user during the past 48 hours.\n\n### False positive analysis\n\n- This rule can be triggered by the manual removal of backup files and by removal using other third-party tools that are\nnot from the backup suite. Exceptions can be added for specific accounts and executables, preferably tied together.\n\n### Related rules\n\n- Deleting Backup Catalogs with Wbadmin - 581add16-df76-42bb-af8e-c979bfb39a59\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n- Volume Shadow Copy Deletion via WMIC - dc9c1f74-dac3-48e3-b47f-eb79db358f57\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the password of the involved accounts.\n- Perform data recovery locally or restore the backups from replicated copies (Cloud, other servers, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Third-party Backup Files Deleted via Unexpected Process\n\nBackups are a significant obstacle for any ransomware operation. They allow the victim to resume business by performing\ndata recovery, making them a valuable target.\n\nAttackers can delete backups from the host and gain access to backup servers to remove centralized backups for the\nenvironment, ensuring that victims have no alternatives to paying the ransom.\n\nThis rule identifies file deletions performed by a process that does not belong to the backup suite and aims to delete\nVeritas or Veeam backups.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- This rule can be triggered by the manual removal of backup files and by removal using other third-party tools that are\nnot from the backup suite. Exceptions can be added for specific accounts and executables, preferably tied together.\n\n### Related rules\n\n- Deleting Backup Catalogs with Wbadmin - 581add16-df76-42bb-af8e-c979bfb39a59\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n- Volume Shadow Copy Deletion via WMIC - dc9c1f74-dac3-48e3-b47f-eb79db358f57\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Perform data recovery locally or restore the backups from replicated copies (Cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "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" @@ -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/impact_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 index 4f7535803cc79..1dc41bd4bab5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Deleting Backup Catalogs with Wbadmin", - "note": "## Triage and analysis\n\n### Investigating Deleting Backup Catalogs with Wbadmin\n\nWindows Server Backup stores the details about your backups (what volumes are backed up and where the backups are\nlocated) in a file called a backup catalog, which ransomware victims can use to recover corrupted backup files.\nDeleting these files is a common step in threat actor playbooks.\n\nThis rule identifies the deletion of the backup catalog using the `wbadmin.exe` utility.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Confirm whether the account owner is aware of the operation.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- Administrators can use this command to delete corrupted catalogs, but overall the activity is unlikely to be legitimate.\n\n### Related rules\n\n- Third-party Backup Files Deleted via Unexpected Process - 11ea6bec-ebde-4d71-a8e9-784948f8e3e9\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n- Volume Shadow Copy Deletion via WMIC - dc9c1f74-dac3-48e3-b47f-eb79db358f57\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Reset the password of the involved accounts.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n- If any backups were affected:\n - Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Deleting Backup Catalogs with Wbadmin\n\nWindows Server Backup stores the details about your backups (what volumes are backed up and where the backups are\nlocated) in a file called a backup catalog, which ransomware victims can use to recover corrupted backup files.\nDeleting these files is a common step in threat actor playbooks.\n\nThis rule identifies the deletion of the backup catalog using the `wbadmin.exe` utility.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- Administrators can use this command to delete corrupted catalogs, but overall the activity is unlikely to be legitimate.\n\n### Related rules\n\n- Third-party Backup Files Deleted via Unexpected Process - 11ea6bec-ebde-4d71-a8e9-784948f8e3e9\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n- Volume Shadow Copy Deletion via WMIC - dc9c1f74-dac3-48e3-b47f-eb79db358f57\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n- If any backups were affected:\n - Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"wbadmin.exe\" or process.pe.original_file_name == \"WBADMIN.EXE\") and\n process.args : \"catalog\" and process.args : \"delete\"\n", "risk_score": 21, "rule_id": "581add16-df76-42bb-af8e-c979bfb39a59", @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 11 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json index 48a6990b9edbf..790c4092ae58f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json @@ -58,5 +58,5 @@ "timeline_title": "Comprehensive File Timeline", "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/impact_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json index e3063f78ffd23..16ee71a7dc17f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Modification of Boot Configuration", - "note": "## Triage and analysis\n\n### Investigating Modification of Boot Configuration\n\nBoot entry parameters, or boot parameters, are optional, system-specific settings that represent configuration options.\nThese are stored in a boot configuration data (BCD) store, and administrators can use utilities like `bcdedit.exe` to\nconfigure these.\n\nThis rule identifies the usage of `bcdedit.exe` to:\n\n- Disable Windows Error Recovery (recoveryenabled).\n- Ignore errors if there is a failed boot, failed shutdown, or failed checkpoint (bootstatuspolicy ignoreallfailures).\n\nThese are common steps in destructive attacks by adversaries leveraging ransomware.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Confirm whether the account owner is aware of the operation.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- The usage of these options is not inherently malicious. Administrators can modify these configurations to force a\nmachine to boot for troubleshooting or data recovery purposes.\n\n### Related rules\n\n- Deleting Backup Catalogs with Wbadmin - 581add16-df76-42bb-af8e-c979bfb39a59\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Reset the password of the involved accounts.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Modification of Boot Configuration\n\nBoot entry parameters, or boot parameters, are optional, system-specific settings that represent configuration options.\nThese are stored in a boot configuration data (BCD) store, and administrators can use utilities like `bcdedit.exe` to\nconfigure these.\n\nThis rule identifies the usage of `bcdedit.exe` to:\n\n- Disable Windows Error Recovery (recoveryenabled).\n- Ignore errors if there is a failed boot, failed shutdown, or failed checkpoint (bootstatuspolicy ignoreallfailures).\n\nThese are common steps in destructive attacks by adversaries leveraging ransomware.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- The usage of these options is not inherently malicious. Administrators can modify these configurations to force a\nmachine to boot for troubleshooting or data recovery purposes.\n\n### Related rules\n\n- Deleting Backup Catalogs with Wbadmin - 581add16-df76-42bb-af8e-c979bfb39a59\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"bcdedit.exe\" or process.pe.original_file_name == \"bcdedit.exe\") and\n (process.args : \"/set\" and process.args : \"bootstatuspolicy\" and process.args : \"ignoreallfailures\") or\n (process.args : \"no\" and process.args : \"recoveryenabled\")\n", "risk_score": 21, "rule_id": "69c251fb-a5d6-4035-b5ec-40438bd829ff", @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 11 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json index 9b86a862caf19..36a30f51beab0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json @@ -12,7 +12,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "High Number of Process and/or Service Terminations", - "note": "## Triage and analysis\n\n### Investigating High Number of Process and/or Service Terminations\n\nAttackers can stop services and kill processes for a variety of purposes. For example, they can stop services associated\nwith business applications and databases to release the lock on files used by these applications so they may be encrypted,\nor stop security and backup solutions, etc.\n\nThis rule identifies a high number (10) of service and/or process terminations (stop, delete, or suspend) from the same\nhost within a short time period.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Confirm whether the account owner is aware of the operation, and why it was performed.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further destructive behavior, which is commonly associated with this activity.\n- Reset the password of the involved accounts.\n- Reimage the host operating system or restore it to the operational state.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n", + "note": "## Triage and analysis\n\n### Investigating High Number of Process and/or Service Terminations\n\nAttackers can stop services and kill processes for a variety of purposes. For example, they can stop services associated\nwith business applications and databases to release the lock on files used by these applications so they may be encrypted,\nor stop security and backup solutions, etc.\n\nThis rule identifies a high number (10) of service and/or process terminations (stop, delete, or suspend) from the same\nhost within a short time period.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further destructive behavior, which is commonly associated with this activity.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system or restore it to the operational state.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.category:process and event.type:start and process.name:(net.exe or sc.exe or taskkill.exe) and\n process.args:(stop or pause or delete or \"/PID\" or \"/IM\" or \"/T\" or \"/F\" or \"/t\" or \"/f\" or \"/im\" or \"/pid\")\n", "risk_score": 47, "rule_id": "035889c4-2686-4583-a7df-67f89c292f2c", @@ -48,5 +48,5 @@ "value": 10 }, "type": "threshold", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_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 index a81ca2b2756b5..6e53615324f82 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deleted or Resized via VssAdmin", - "note": "## Triage and analysis.\n\n### Investigating Volume Shadow Copy Deleted or Resized via VssAdmin\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of Vssadmin.exe to either delete or resize shadow copies.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Confirm whether the account owner is aware of the operation.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- If unsigned files are found on the process tree:\n - Capture copies of the files.\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule may produce benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If malware was found, isolate the involved hosts to prevent the infection of other hosts.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Reset the password of the involved accounts.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deleted or Resized via VssAdmin\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of Vssadmin.exe to either delete or resize shadow copies.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule may produce benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\")\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", @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 12 + "version": 13 } 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 index c67b8947dfcf5..424029ea8cb11 100644 --- 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 @@ -13,7 +13,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deletion via PowerShell", - "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via PowerShell\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of PowerShell cmdlets to interact with the Win32_ShadowCopy WMI class, retrieve shadow\ncopy objects, and delete them.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Confirm whether the account owner is aware of the operation.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- If unsigned files are found on the process tree:\n - Capture copies of the files.\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts of contacting external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If malware was found, isolate the involved hosts to prevent the infection of other hosts.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Reset the password of the involved accounts.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via PowerShell\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of PowerShell cmdlets to interact with the Win32_ShadowCopy WMI class, retrieve shadow\ncopy objects, and delete them.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.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", @@ -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/impact_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 index 85660d00e557e..a61de760359db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Volume Shadow Copy Deletion via WMIC", - "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via WMIC\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of `wmic.exe` to interact with VSS via the `shadowcopy` alias and delete parameter.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Confirm whether the account owner is aware of the operation.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- If unsigned files are found on the process tree:\n - Capture copies of the files.\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts of contacting external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- If malware was found, isolate the involved hosts to prevent the infection of other hosts.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Reset the password of the involved accounts.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Volume Shadow Copy Deletion via WMIC\n\nThe Volume Shadow Copy Service (VSS) is a Windows feature that enables system administrators to take snapshots of volumes\nthat can later be restored or mounted to recover specific files or folders.\n\nA typical step in the playbook of an attacker attempting to deploy ransomware is to delete Volume Shadow\nCopies to ensure that victims have no alternative to paying the ransom, making any action that deletes shadow\ncopies worth monitoring.\n\nThis rule monitors the execution of `wmic.exe` to interact with VSS via the `shadowcopy` alias and delete parameter.\n\n#### Possible investigation steps\n\n- Investigate the program execution chain (parent process tree).\n- Check whether the account is authorized to perform this operation.\n- Contact the account owner and confirm whether they are aware of this activity.\n- In the case of a resize operation, check if the resize value is equal to suspicious values, like 401MB.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences in other hosts.\n- Check if any files on the host machine have been encrypted.\n\n\n### False positive analysis\n\n- This rule has chances of producing benign true positives (B-TPs). If this activity is expected and noisy in your\nenvironment, consider adding exceptions \u2014 preferably with a combination of user and command line conditions.\n\n### Related rules\n\n- Volume Shadow Copy Deleted or Resized via VssAdmin - b5ea4bfe-a1b2-421f-9d47-22a75a6f2921\n- Volume Shadow Copy Deletion via PowerShell - d99a037b-c8e2-47a5-97b9-170d076827c4\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Priority should be given due to the advanced stage of this activity on the attack.\n- Consider isolating the involved host to prevent destructive behavior, which is commonly associated with this activity.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If data was encrypted, deleted, or modified, activate your data recovery plan.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Perform data recovery locally or restore the backups from replicated copies (cloud, other servers, etc.).\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n (process.name : \"WMIC.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n process.args : \"delete\" and process.args : \"shadowcopy\"\n", "risk_score": 73, "rule_id": "dc9c1f74-dac3-48e3-b47f-eb79db358f57", @@ -43,5 +43,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 11 + "version": 12 } 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 6bf4a1d804a9c..e579cb36edf0d 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 @@ -48,40 +48,40 @@ 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'; -import rule67 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule68 from './initial_access_suspicious_ms_office_child_process.json'; -import rule69 from './initial_access_suspicious_ms_outlook_child_process.json'; -import rule70 from './lateral_movement_direct_outbound_smb_connection.json'; -import rule71 from './lateral_movement_service_control_spawned_script_int.json'; -import rule72 from './linux_hping_activity.json'; +import rule39 from './discovery_linux_hping_activity.json'; +import rule40 from './discovery_whoami_command_activity.json'; +import rule41 from './endgame_adversary_behavior_detected.json'; +import rule42 from './endgame_cred_dumping_detected.json'; +import rule43 from './endgame_cred_dumping_prevented.json'; +import rule44 from './endgame_cred_manipulation_detected.json'; +import rule45 from './endgame_cred_manipulation_prevented.json'; +import rule46 from './endgame_exploit_detected.json'; +import rule47 from './endgame_exploit_prevented.json'; +import rule48 from './endgame_malware_detected.json'; +import rule49 from './endgame_malware_prevented.json'; +import rule50 from './endgame_permission_theft_detected.json'; +import rule51 from './endgame_permission_theft_prevented.json'; +import rule52 from './endgame_process_injection_detected.json'; +import rule53 from './endgame_process_injection_prevented.json'; +import rule54 from './endgame_ransomware_detected.json'; +import rule55 from './endgame_ransomware_prevented.json'; +import rule56 from './execution_command_prompt_connecting_to_the_internet.json'; +import rule57 from './execution_command_shell_started_by_svchost.json'; +import rule58 from './execution_html_help_executable_program_connecting_to_the_internet.json'; +import rule59 from './execution_psexec_lateral_movement_command.json'; +import rule60 from './execution_register_server_program_connecting_to_the_internet.json'; +import rule61 from './execution_via_compiled_html_file.json'; +import rule62 from './impact_deleting_backup_catalogs_with_wbadmin.json'; +import rule63 from './impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json'; +import rule64 from './impact_volume_shadow_copy_deletion_via_wmic.json'; +import rule65 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; +import rule66 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; +import rule67 from './initial_access_script_executing_powershell.json'; +import rule68 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule69 from './initial_access_suspicious_ms_office_child_process.json'; +import rule70 from './initial_access_suspicious_ms_outlook_child_process.json'; +import rule71 from './lateral_movement_direct_outbound_smb_connection.json'; +import rule72 from './lateral_movement_service_control_spawned_script_int.json'; import rule73 from './linux_iodine_activity.json'; import rule74 from './linux_netcat_network_connection.json'; import rule75 from './linux_nping_activity.json'; @@ -98,7 +98,7 @@ import rule85 from './privilege_escalation_unusual_parentchild_relationship.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'; +import rule89 from './discovery_command_system_account.json'; import rule90 from './command_and_control_certutil_network_connection.json'; import rule91 from './defense_evasion_cve_2020_0601.json'; import rule92 from './credential_access_credential_dumping_msbuild.json'; @@ -110,581 +110,578 @@ import rule97 from './defense_evasion_execution_msbuild_started_unusal_process.j import rule98 from './defense_evasion_injection_msbuild.json'; import rule99 from './ml_linux_anomalous_network_activity.json'; import rule100 from './ml_linux_anomalous_network_port_activity.json'; -import rule101 from './ml_linux_anomalous_network_service.json'; -import rule102 from './ml_linux_anomalous_network_url_activity.json'; -import rule103 from './ml_linux_anomalous_process_all_hosts.json'; -import rule104 from './ml_linux_anomalous_user_name.json'; -import rule105 from './ml_packetbeat_dns_tunneling.json'; -import rule106 from './ml_packetbeat_rare_dns_question.json'; -import rule107 from './ml_packetbeat_rare_server_domain.json'; -import rule108 from './ml_packetbeat_rare_urls.json'; -import rule109 from './ml_packetbeat_rare_user_agent.json'; -import rule110 from './ml_rare_process_by_host_linux.json'; -import rule111 from './ml_rare_process_by_host_windows.json'; -import rule112 from './ml_suspicious_login_activity.json'; -import rule113 from './ml_windows_anomalous_network_activity.json'; -import rule114 from './ml_windows_anomalous_path_activity.json'; -import rule115 from './ml_windows_anomalous_process_all_hosts.json'; -import rule116 from './ml_windows_anomalous_process_creation.json'; -import rule117 from './ml_windows_anomalous_script.json'; -import rule118 from './ml_windows_anomalous_service.json'; -import rule119 from './ml_windows_anomalous_user_name.json'; -import rule120 from './ml_windows_rare_user_runas_event.json'; -import rule121 from './ml_windows_rare_user_type10_remote_login.json'; -import rule122 from './execution_suspicious_pdf_reader.json'; -import rule123 from './privilege_escalation_sudoers_file_mod.json'; -import rule124 from './defense_evasion_iis_httplogging_disabled.json'; -import rule125 from './execution_python_tty_shell.json'; -import rule126 from './execution_perl_tty_shell.json'; -import rule127 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; -import rule128 from './defense_evasion_file_mod_writable_dir.json'; -import rule129 from './defense_evasion_disable_selinux_attempt.json'; -import rule130 from './discovery_kernel_module_enumeration.json'; -import rule131 from './lateral_movement_telnet_network_activity_external.json'; -import rule132 from './lateral_movement_telnet_network_activity_internal.json'; -import rule133 from './privilege_escalation_setuid_setgid_bit_set_via_chmod.json'; -import rule134 from './defense_evasion_attempt_to_disable_iptables_or_firewall.json'; -import rule135 from './defense_evasion_kernel_module_removal.json'; -import rule136 from './defense_evasion_attempt_to_disable_syslog_service.json'; -import rule137 from './defense_evasion_file_deletion_via_shred.json'; -import rule138 from './discovery_virtual_machine_fingerprinting.json'; -import rule139 from './defense_evasion_hidden_file_dir_tmp.json'; -import rule140 from './defense_evasion_deletion_of_bash_command_line_history.json'; -import rule141 from './impact_cloudwatch_log_group_deletion.json'; -import rule142 from './impact_cloudwatch_log_stream_deletion.json'; -import rule143 from './impact_rds_instance_cluster_stoppage.json'; -import rule144 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; -import rule145 from './persistence_rds_cluster_creation.json'; -import rule146 from './credential_access_attempted_bypass_of_okta_mfa.json'; -import rule147 from './defense_evasion_waf_acl_deletion.json'; -import rule148 from './impact_attempt_to_revoke_okta_api_token.json'; -import rule149 from './impact_iam_group_deletion.json'; -import rule150 from './impact_possible_okta_dos_attack.json'; -import rule151 from './impact_rds_instance_cluster_deletion.json'; -import rule152 from './initial_access_suspicious_activity_reported_by_okta_user.json'; -import rule153 from './okta_attempt_to_deactivate_okta_policy.json'; -import rule154 from './okta_attempt_to_deactivate_okta_policy_rule.json'; -import rule155 from './okta_attempt_to_modify_okta_network_zone.json'; -import rule156 from './okta_attempt_to_modify_okta_policy.json'; -import rule157 from './okta_attempt_to_modify_okta_policy_rule.json'; -import rule158 from './okta_threat_detected_by_okta_threatinsight.json'; -import rule159 from './persistence_administrator_privileges_assigned_to_okta_group.json'; -import rule160 from './persistence_attempt_to_create_okta_api_token.json'; -import rule161 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; -import rule162 from './defense_evasion_cloudtrail_logging_deleted.json'; -import rule163 from './defense_evasion_ec2_network_acl_deletion.json'; -import rule164 from './impact_iam_deactivate_mfa_device.json'; -import rule165 from './defense_evasion_s3_bucket_configuration_deletion.json'; -import rule166 from './defense_evasion_guardduty_detector_deletion.json'; -import rule167 from './okta_attempt_to_delete_okta_policy.json'; -import rule168 from './credential_access_iam_user_addition_to_group.json'; -import rule169 from './persistence_ec2_network_acl_creation.json'; -import rule170 from './impact_ec2_disable_ebs_encryption.json'; -import rule171 from './persistence_iam_group_creation.json'; -import rule172 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; -import rule173 from './collection_cloudtrail_logging_created.json'; -import rule174 from './defense_evasion_cloudtrail_logging_suspended.json'; -import rule175 from './impact_cloudtrail_logging_updated.json'; -import rule176 from './initial_access_console_login_root.json'; -import rule177 from './defense_evasion_cloudwatch_alarm_deletion.json'; -import rule178 from './defense_evasion_ec2_flow_log_deletion.json'; -import rule179 from './defense_evasion_configuration_recorder_stopped.json'; -import rule180 from './exfiltration_ec2_snapshot_change_activity.json'; -import rule181 from './defense_evasion_config_service_rule_deletion.json'; -import rule182 from './okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; -import rule183 from './command_and_control_download_rar_powershell_from_internet.json'; -import rule184 from './initial_access_password_recovery.json'; -import rule185 from './command_and_control_cobalt_strike_beacon.json'; -import rule186 from './command_and_control_fin7_c2_behavior.json'; -import rule187 from './command_and_control_halfbaked_beacon.json'; -import rule188 from './credential_access_secretsmanager_getsecretvalue.json'; -import rule189 from './initial_access_via_system_manager.json'; -import rule190 from './privilege_escalation_root_login_without_mfa.json'; -import rule191 from './privilege_escalation_updateassumerolepolicy.json'; -import rule192 from './impact_hosts_file_modified.json'; -import rule193 from './elastic_endpoint_security.json'; -import rule194 from './external_alerts.json'; -import rule195 from './initial_access_login_failures.json'; -import rule196 from './initial_access_login_location.json'; -import rule197 from './initial_access_login_sessions.json'; -import rule198 from './initial_access_login_time.json'; -import rule199 from './ml_cloudtrail_error_message_spike.json'; -import rule200 from './ml_cloudtrail_rare_error_code.json'; -import rule201 from './ml_cloudtrail_rare_method_by_city.json'; -import rule202 from './ml_cloudtrail_rare_method_by_country.json'; -import rule203 from './ml_cloudtrail_rare_method_by_user.json'; -import rule204 from './credential_access_aws_iam_assume_role_brute_force.json'; -import rule205 from './credential_access_okta_brute_force_or_password_spraying.json'; -import rule206 from './initial_access_unusual_dns_service_children.json'; -import rule207 from './initial_access_unusual_dns_service_file_writes.json'; -import rule208 from './lateral_movement_dns_server_overflow.json'; -import rule209 from './credential_access_root_console_failure_brute_force.json'; -import rule210 from './initial_access_unsecure_elasticsearch_node.json'; -import rule211 from './impact_virtual_network_device_modified.json'; -import rule212 from './credential_access_domain_backup_dpapi_private_keys.json'; -import rule213 from './persistence_gpo_schtask_service_creation.json'; -import rule214 from './credential_access_credentials_keychains.json'; -import rule215 from './credential_access_kerberosdump_kcc.json'; -import rule216 from './defense_evasion_attempt_del_quarantine_attrib.json'; -import rule217 from './execution_suspicious_psexesvc.json'; -import rule218 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; -import rule219 from './privilege_escalation_printspooler_service_suspicious_file.json'; -import rule220 from './privilege_escalation_printspooler_suspicious_spl_file.json'; -import rule221 from './defense_evasion_azure_diagnostic_settings_deletion.json'; -import rule222 from './execution_command_virtual_machine.json'; -import rule223 from './execution_via_hidden_shell_conhost.json'; -import rule224 from './impact_resource_group_deletion.json'; -import rule225 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; -import rule226 from './persistence_via_update_orchestrator_service_hijack.json'; -import rule227 from './collection_update_event_hub_auth_rule.json'; -import rule228 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; -import rule229 from './credential_access_iis_connectionstrings_dumping.json'; -import rule230 from './defense_evasion_event_hub_deletion.json'; -import rule231 from './defense_evasion_firewall_policy_deletion.json'; -import rule232 from './defense_evasion_sdelete_like_filename_rename.json'; -import rule233 from './lateral_movement_remote_ssh_login_enabled.json'; -import rule234 from './persistence_azure_automation_account_created.json'; -import rule235 from './persistence_azure_automation_runbook_created_or_modified.json'; -import rule236 from './persistence_azure_automation_webhook_created.json'; -import rule237 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; -import rule238 from './credential_access_attempts_to_brute_force_okta_user_account.json'; -import rule239 from './credential_access_storage_account_key_regenerated.json'; -import rule240 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; -import rule241 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; -import rule242 from './defense_evasion_unusual_system_vp_child_program.json'; -import rule243 from './discovery_blob_container_access_mod.json'; -import rule244 from './persistence_mfa_disabled_for_azure_user.json'; -import rule245 from './persistence_user_added_as_owner_for_azure_application.json'; -import rule246 from './persistence_user_added_as_owner_for_azure_service_principal.json'; -import rule247 from './defense_evasion_dotnet_compiler_parent_process.json'; -import rule248 from './defense_evasion_suspicious_managedcode_host_process.json'; -import rule249 from './execution_command_shell_started_by_unusual_process.json'; -import rule250 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; -import rule251 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; -import rule252 from './defense_evasion_masquerading_werfault.json'; -import rule253 from './credential_access_key_vault_modified.json'; -import rule254 from './credential_access_mimikatz_memssp_default_logs.json'; -import rule255 from './defense_evasion_code_injection_conhost.json'; -import rule256 from './defense_evasion_network_watcher_deletion.json'; -import rule257 from './initial_access_external_guest_user_invite.json'; -import rule258 from './defense_evasion_masquerading_renamed_autoit.json'; -import rule259 from './impact_azure_automation_runbook_deleted.json'; -import rule260 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; -import rule261 from './persistence_azure_conditional_access_policy_modified.json'; -import rule262 from './persistence_azure_privileged_identity_management_role_modified.json'; -import rule263 from './command_and_control_teamviewer_remote_file_copy.json'; -import rule264 from './defense_evasion_installutil_beacon.json'; -import rule265 from './defense_evasion_mshta_beacon.json'; -import rule266 from './defense_evasion_network_connection_from_windows_binary.json'; -import rule267 from './defense_evasion_rundll32_no_arguments.json'; -import rule268 from './defense_evasion_suspicious_scrobj_load.json'; -import rule269 from './defense_evasion_suspicious_wmi_script.json'; -import rule270 from './execution_ms_office_written_file.json'; -import rule271 from './execution_pdf_written_file.json'; -import rule272 from './lateral_movement_cmd_service.json'; -import rule273 from './persistence_app_compat_shim.json'; -import rule274 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; -import rule275 from './command_and_control_remote_file_copy_mpcmdrun.json'; -import rule276 from './defense_evasion_execution_suspicious_explorer_winword.json'; -import rule277 from './defense_evasion_suspicious_zoom_child_process.json'; -import rule278 from './ml_linux_anomalous_compiler_activity.json'; -import rule279 from './ml_linux_anomalous_kernel_module_arguments.json'; -import rule280 from './ml_linux_anomalous_sudo_activity.json'; -import rule281 from './ml_linux_system_information_discovery.json'; -import rule282 from './ml_linux_system_network_configuration_discovery.json'; -import rule283 from './ml_linux_system_network_connection_discovery.json'; -import rule284 from './ml_linux_system_process_discovery.json'; -import rule285 from './ml_linux_system_user_discovery.json'; -import rule286 from './discovery_post_exploitation_external_ip_lookup.json'; -import rule287 from './initial_access_zoom_meeting_with_no_passcode.json'; -import rule288 from './defense_evasion_gcp_logging_sink_deletion.json'; -import rule289 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; -import rule290 from './defense_evasion_gcp_firewall_rule_created.json'; -import rule291 from './defense_evasion_gcp_firewall_rule_deleted.json'; -import rule292 from './defense_evasion_gcp_firewall_rule_modified.json'; -import rule293 from './defense_evasion_gcp_logging_bucket_deletion.json'; -import rule294 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; -import rule295 from './impact_gcp_storage_bucket_deleted.json'; -import rule296 from './initial_access_gcp_iam_custom_role_creation.json'; -import rule297 from './persistence_gcp_iam_service_account_key_deletion.json'; -import rule298 from './persistence_gcp_key_created_for_service_account.json'; -import rule299 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; -import rule300 from './exfiltration_gcp_logging_sink_modification.json'; -import rule301 from './impact_gcp_iam_role_deletion.json'; -import rule302 from './impact_gcp_service_account_deleted.json'; -import rule303 from './impact_gcp_service_account_disabled.json'; -import rule304 from './impact_gcp_virtual_private_cloud_network_deleted.json'; -import rule305 from './impact_gcp_virtual_private_cloud_route_created.json'; -import rule306 from './impact_gcp_virtual_private_cloud_route_deleted.json'; -import rule307 from './ml_linux_anomalous_metadata_process.json'; -import rule308 from './ml_linux_anomalous_metadata_user.json'; -import rule309 from './ml_windows_anomalous_metadata_process.json'; -import rule310 from './ml_windows_anomalous_metadata_user.json'; -import rule311 from './persistence_gcp_service_account_created.json'; -import rule312 from './collection_gcp_pub_sub_subscription_creation.json'; -import rule313 from './collection_gcp_pub_sub_topic_creation.json'; -import rule314 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; -import rule315 from './persistence_azure_pim_user_added_global_admin.json'; -import rule316 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; -import rule317 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; -import rule318 from './defense_evasion_execution_lolbas_wuauclt.json'; -import rule319 from './privilege_escalation_unusual_svchost_childproc_childless.json'; -import rule320 from './command_and_control_rdp_tunnel_plink.json'; -import rule321 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; -import rule322 from './discovery_privileged_localgroup_membership.json'; -import rule323 from './persistence_ms_office_addins_file.json'; -import rule324 from './discovery_adfind_command_activity.json'; -import rule325 from './discovery_security_software_wmic.json'; -import rule326 from './execution_command_shell_via_rundll32.json'; -import rule327 from './execution_suspicious_cmd_wmi.json'; -import rule328 from './lateral_movement_via_startup_folder_rdp_smb.json'; -import rule329 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; -import rule330 from './privilege_escalation_uac_bypass_mock_windir.json'; -import rule331 from './defense_evasion_potential_processherpaderping.json'; -import rule332 from './privilege_escalation_uac_bypass_dll_sideloading.json'; -import rule333 from './execution_shared_modules_local_sxs_dll.json'; -import rule334 from './privilege_escalation_uac_bypass_com_clipup.json'; -import rule335 from './initial_access_via_explorer_suspicious_child_parent_args.json'; -import rule336 from './execution_from_unusual_directory.json'; -import rule337 from './execution_from_unusual_path_cmdline.json'; -import rule338 from './credential_access_kerberoasting_unusual_process.json'; -import rule339 from './discovery_peripheral_device.json'; -import rule340 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; -import rule341 from './defense_evasion_deleting_websvr_access_logs.json'; -import rule342 from './defense_evasion_log_files_deleted.json'; -import rule343 from './defense_evasion_timestomp_touch.json'; -import rule344 from './lateral_movement_dcom_hta.json'; -import rule345 from './lateral_movement_execution_via_file_shares_sequence.json'; -import rule346 from './privilege_escalation_uac_bypass_com_ieinstal.json'; -import rule347 from './command_and_control_common_webservices.json'; -import rule348 from './command_and_control_encrypted_channel_freesslcert.json'; -import rule349 from './defense_evasion_process_termination_followed_by_deletion.json'; -import rule350 from './lateral_movement_remote_file_copy_hidden_share.json'; -import rule351 from './attempt_to_deactivate_okta_network_zone.json'; -import rule352 from './attempt_to_delete_okta_network_zone.json'; -import rule353 from './lateral_movement_dcom_mmc20.json'; -import rule354 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; -import rule355 from './okta_attempt_to_deactivate_okta_application.json'; -import rule356 from './okta_attempt_to_delete_okta_application.json'; -import rule357 from './okta_attempt_to_delete_okta_policy_rule.json'; -import rule358 from './okta_attempt_to_modify_okta_application.json'; -import rule359 from './persistence_administrator_role_assigned_to_okta_user.json'; -import rule360 from './lateral_movement_executable_tool_transfer_smb.json'; -import rule361 from './command_and_control_dns_tunneling_nslookup.json'; -import rule362 from './lateral_movement_execution_from_tsclient_mup.json'; -import rule363 from './lateral_movement_rdp_sharprdp_target.json'; -import rule364 from './defense_evasion_clearing_windows_security_logs.json'; -import rule365 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; -import rule366 from './execution_suspicious_short_program_name.json'; -import rule367 from './lateral_movement_incoming_wmi.json'; -import rule368 from './persistence_via_hidden_run_key_valuename.json'; -import rule369 from './credential_access_potential_ssh_bruteforce.json'; -import rule370 from './credential_access_promt_for_pwd_via_osascript.json'; -import rule371 from './lateral_movement_remote_services.json'; -import rule372 from './application_added_to_google_workspace_domain.json'; -import rule373 from './domain_added_to_google_workspace_trusted_domains.json'; -import rule374 from './execution_suspicious_image_load_wmi_ms_office.json'; -import rule375 from './execution_suspicious_powershell_imgload.json'; -import rule376 from './google_workspace_admin_role_deletion.json'; -import rule377 from './google_workspace_mfa_enforcement_disabled.json'; -import rule378 from './google_workspace_policy_modified.json'; -import rule379 from './mfa_disabled_for_google_workspace_organization.json'; -import rule380 from './persistence_evasion_registry_ifeo_injection.json'; -import rule381 from './persistence_google_workspace_admin_role_assigned_to_user.json'; -import rule382 from './persistence_google_workspace_custom_admin_role_created.json'; -import rule383 from './persistence_google_workspace_role_modified.json'; -import rule384 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; -import rule385 from './defense_evasion_masquerading_trusted_directory.json'; -import rule386 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; -import rule387 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; -import rule388 from './microsoft_365_exchange_dkim_signing_config_disabled.json'; -import rule389 from './persistence_appcertdlls_registry.json'; -import rule390 from './persistence_appinitdlls_registry.json'; -import rule391 from './persistence_registry_uncommon.json'; -import rule392 from './persistence_run_key_and_startup_broad.json'; -import rule393 from './persistence_services_registry.json'; -import rule394 from './persistence_startup_folder_file_written_by_suspicious_process.json'; -import rule395 from './persistence_startup_folder_scripts.json'; -import rule396 from './persistence_suspicious_com_hijack_registry.json'; -import rule397 from './persistence_via_lsa_security_support_provider_registry.json'; -import rule398 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; -import rule399 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; -import rule400 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; -import rule401 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; -import rule402 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; -import rule403 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; -import rule404 from './lateral_movement_suspicious_rdp_client_imageload.json'; -import rule405 from './persistence_runtime_run_key_startup_susp_procs.json'; -import rule406 from './persistence_suspicious_scheduled_task_runtime.json'; -import rule407 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; -import rule408 from './lateral_movement_scheduled_task_target.json'; -import rule409 from './persistence_microsoft_365_exchange_management_role_assignment.json'; -import rule410 from './persistence_microsoft_365_teams_guest_access_enabled.json'; -import rule411 from './credential_access_dump_registry_hives.json'; -import rule412 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; -import rule413 from './persistence_ms_outlook_vba_template.json'; -import rule414 from './persistence_suspicious_service_created_registry.json'; -import rule415 from './privilege_escalation_named_pipe_impersonation.json'; -import rule416 from './credential_access_cmdline_dump_tool.json'; -import rule417 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; -import rule418 from './credential_access_lsass_memdump_file_created.json'; -import rule419 from './lateral_movement_incoming_winrm_shell_execution.json'; -import rule420 from './lateral_movement_powershell_remoting_target.json'; -import rule421 from './command_and_control_port_forwarding_added_registry.json'; -import rule422 from './defense_evasion_hide_encoded_executable_registry.json'; -import rule423 from './lateral_movement_rdp_enabled_registry.json'; -import rule424 from './privilege_escalation_printspooler_registry_copyfiles.json'; -import rule425 from './privilege_escalation_rogue_windir_environment_var.json'; -import rule426 from './initial_access_scripts_process_started_via_wmi.json'; -import rule427 from './command_and_control_iexplore_via_com.json'; -import rule428 from './command_and_control_remote_file_copy_scripts.json'; -import rule429 from './persistence_local_scheduled_task_scripting.json'; -import rule430 from './persistence_startup_folder_file_written_by_unsigned_process.json'; -import rule431 from './command_and_control_remote_file_copy_powershell.json'; -import rule432 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; -import rule433 from './microsoft_365_teams_custom_app_interaction_allowed.json'; -import rule434 from './persistence_microsoft_365_teams_external_access_enabled.json'; -import rule435 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; -import rule436 from './impact_stop_process_service_threshold.json'; -import rule437 from './collection_winrar_encryption.json'; -import rule438 from './defense_evasion_unusual_dir_ads.json'; -import rule439 from './discovery_admin_recon.json'; -import rule440 from './discovery_file_dir_discovery.json'; -import rule441 from './discovery_net_view.json'; -import rule442 from './discovery_remote_system_discovery_commands_windows.json'; -import rule443 from './persistence_via_windows_management_instrumentation_event_subscription.json'; -import rule444 from './execution_scripting_osascript_exec_followed_by_netcon.json'; -import rule445 from './execution_shell_execution_via_apple_scripting.json'; -import rule446 from './persistence_creation_change_launch_agents_file.json'; -import rule447 from './persistence_creation_modif_launch_deamon_sequence.json'; -import rule448 from './persistence_folder_action_scripts_runtime.json'; -import rule449 from './persistence_login_logout_hooks_defaults.json'; -import rule450 from './privilege_escalation_explicit_creds_via_scripting.json'; -import rule451 from './command_and_control_sunburst_c2_activity_detected.json'; -import rule452 from './defense_evasion_azure_application_credential_modification.json'; -import rule453 from './defense_evasion_azure_service_principal_addition.json'; -import rule454 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; -import rule455 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; -import rule456 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; -import rule457 from './initial_access_azure_active_directory_powershell_signin.json'; -import rule458 from './collection_email_powershell_exchange_mailbox.json'; -import rule459 from './execution_scheduled_task_powershell_source.json'; -import rule460 from './persistence_powershell_exch_mailbox_activesync_add_device.json'; -import rule461 from './persistence_docker_shortcuts_plist_modification.json'; -import rule462 from './persistence_evasion_hidden_local_account_creation.json'; -import rule463 from './persistence_finder_sync_plugin_pluginkit.json'; -import rule464 from './discovery_security_software_grep.json'; -import rule465 from './credential_access_cookies_chromium_browsers_debugging.json'; -import rule466 from './credential_access_ssh_backdoor_log.json'; -import rule467 from './persistence_credential_access_modify_auth_module_or_config.json'; -import rule468 from './persistence_credential_access_modify_ssh_binaries.json'; -import rule469 from './credential_access_collection_sensitive_files.json'; -import rule470 from './persistence_ssh_authorized_keys_modification.json'; -import rule471 from './defense_evasion_defender_disabled_via_registry.json'; -import rule472 from './defense_evasion_privacy_controls_tcc_database_modification.json'; -import rule473 from './execution_initial_access_suspicious_browser_childproc.json'; -import rule474 from './execution_script_via_automator_workflows.json'; -import rule475 from './persistence_modification_sublime_app_plugin_or_script.json'; -import rule476 from './privilege_escalation_applescript_with_admin_privs.json'; -import rule477 from './credential_access_dumping_keychain_security.json'; -import rule478 from './initial_access_azure_active_directory_high_risk_signin.json'; -import rule479 from './initial_access_suspicious_mac_ms_office_child_process.json'; -import rule480 from './credential_access_mitm_localhost_webproxy.json'; -import rule481 from './persistence_kde_autostart_modification.json'; -import rule482 from './persistence_user_account_added_to_privileged_group_ad.json'; -import rule483 from './defense_evasion_attempt_to_disable_gatekeeper.json'; -import rule484 from './defense_evasion_sandboxed_office_app_suspicious_zip_file.json'; -import rule485 from './persistence_emond_rules_file_creation.json'; -import rule486 from './persistence_emond_rules_process_execution.json'; -import rule487 from './discovery_users_domain_built_in_commands.json'; -import rule488 from './execution_pentest_eggshell_remote_admin_tool.json'; -import rule489 from './defense_evasion_install_root_certificate.json'; -import rule490 from './persistence_credential_access_authorization_plugin_creation.json'; -import rule491 from './persistence_directory_services_plugins_modification.json'; -import rule492 from './defense_evasion_modify_environment_launchctl.json'; -import rule493 from './defense_evasion_safari_config_change.json'; -import rule494 from './defense_evasion_apple_softupdates_modification.json'; -import rule495 from './credential_access_mod_wdigest_security_provider.json'; -import rule496 from './credential_access_saved_creds_vaultcmd.json'; -import rule497 from './defense_evasion_file_creation_mult_extension.json'; -import rule498 from './execution_enumeration_via_wmiprvse.json'; -import rule499 from './execution_suspicious_jar_child_process.json'; -import rule500 from './persistence_shell_profile_modification.json'; -import rule501 from './persistence_suspicious_calendar_modification.json'; -import rule502 from './persistence_time_provider_mod.json'; -import rule503 from './privilege_escalation_exploit_adobe_acrobat_updater.json'; -import rule504 from './defense_evasion_sip_provider_mod.json'; -import rule505 from './execution_com_object_xwizard.json'; -import rule506 from './privilege_escalation_disable_uac_registry.json'; -import rule507 from './defense_evasion_unusual_ads_file_creation.json'; -import rule508 from './persistence_loginwindow_plist_modification.json'; -import rule509 from './persistence_periodic_tasks_file_mdofiy.json'; -import rule510 from './persistence_via_atom_init_file_modification.json'; -import rule511 from './privilege_escalation_lsa_auth_package.json'; -import rule512 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; -import rule513 from './credential_access_dumping_hashes_bi_cmds.json'; -import rule514 from './lateral_movement_mounting_smb_share.json'; -import rule515 from './privilege_escalation_echo_nopasswd_sudoers.json'; -import rule516 from './privilege_escalation_ld_preload_shared_object_modif.json'; -import rule517 from './privilege_escalation_root_crontab_filemod.json'; -import rule518 from './defense_evasion_create_mod_root_certificate.json'; -import rule519 from './privilege_escalation_sudo_buffer_overflow.json'; -import rule520 from './execution_installer_spawned_network_event.json'; -import rule521 from './initial_access_suspicious_ms_exchange_files.json'; -import rule522 from './initial_access_suspicious_ms_exchange_process.json'; -import rule523 from './initial_access_suspicious_ms_exchange_worker_child_process.json'; -import rule524 from './persistence_evasion_registry_startup_shell_folder_modified.json'; -import rule525 from './persistence_local_scheduled_job_creation.json'; -import rule526 from './persistence_via_wmi_stdregprov_run_services.json'; -import rule527 from './credential_access_persistence_network_logon_provider_modification.json'; -import rule528 from './lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json'; -import rule529 from './collection_microsoft_365_new_inbox_rule.json'; -import rule530 from './ml_high_count_network_denies.json'; -import rule531 from './ml_high_count_network_events.json'; -import rule532 from './ml_rare_destination_country.json'; -import rule533 from './ml_spike_in_traffic_to_a_country.json'; -import rule534 from './command_and_control_tunneling_via_earthworm.json'; -import rule535 from './lateral_movement_evasion_rdp_shadowing.json'; -import rule536 from './threat_intel_fleet_integrations.json'; -import rule537 from './exfiltration_ec2_vm_export_failure.json'; -import rule538 from './exfiltration_ec2_full_network_packet_capture_detected.json'; -import rule539 from './impact_azure_service_principal_credentials_added.json'; -import rule540 from './persistence_ec2_security_group_configuration_change_detection.json'; -import rule541 from './defense_evasion_disabling_windows_logs.json'; -import rule542 from './persistence_route_53_domain_transfer_lock_disabled.json'; -import rule543 from './persistence_route_53_domain_transferred_to_another_account.json'; -import rule544 from './initial_access_okta_user_attempted_unauthorized_access.json'; -import rule545 from './credential_access_user_excessive_sso_logon_errors.json'; -import rule546 from './persistence_exchange_suspicious_mailbox_right_delegation.json'; -import rule547 from './privilege_escalation_new_or_modified_federation_domain.json'; -import rule548 from './privilege_escalation_sts_assumerole_usage.json'; -import rule549 from './privilege_escalation_sts_getsessiontoken_abuse.json'; -import rule550 from './defense_evasion_suspicious_execution_from_mounted_device.json'; -import rule551 from './defense_evasion_unusual_network_connection_via_dllhost.json'; -import rule552 from './defense_evasion_amsienable_key_mod.json'; -import rule553 from './impact_rds_group_deletion.json'; -import rule554 from './persistence_rds_group_creation.json'; -import rule555 from './persistence_route_table_created.json'; -import rule556 from './persistence_route_table_modified_or_deleted.json'; -import rule557 from './exfiltration_rds_snapshot_export.json'; -import rule558 from './persistence_rds_instance_creation.json'; -import rule559 from './privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json'; -import rule560 from './ml_auth_rare_hour_for_a_user_to_logon.json'; -import rule561 from './ml_auth_rare_source_ip_for_a_user.json'; -import rule562 from './ml_auth_rare_user_logon.json'; -import rule563 from './ml_auth_spike_in_failed_logon_events.json'; -import rule564 from './ml_auth_spike_in_logon_events.json'; -import rule565 from './ml_auth_spike_in_logon_events_from_a_source_ip.json'; -import rule566 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; -import rule567 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; -import rule568 from './defense_evasion_kubernetes_events_deleted.json'; -import rule569 from './impact_kubernetes_pod_deleted.json'; -import rule570 from './exfiltration_rds_snapshot_restored.json'; -import rule571 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; -import rule572 from './privilege_escalation_unusual_printspooler_childprocess.json'; -import rule573 from './defense_evasion_disabling_windows_defender_powershell.json'; -import rule574 from './defense_evasion_enable_network_discovery_with_netsh.json'; -import rule575 from './defense_evasion_execution_windefend_unusual_path.json'; -import rule576 from './defense_evasion_agent_spoofing_mismatched_id.json'; -import rule577 from './defense_evasion_agent_spoofing_multiple_hosts.json'; -import rule578 from './defense_evasion_parent_process_pid_spoofing.json'; -import rule579 from './impact_microsoft_365_potential_ransomware_activity.json'; -import rule580 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; -import rule581 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; -import rule582 from './defense_evasion_elasticache_security_group_creation.json'; -import rule583 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; -import rule584 from './impact_volume_shadow_copy_deletion_via_powershell.json'; -import rule585 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; -import rule586 from './defense_evasion_defender_exclusion_via_powershell.json'; -import rule587 from './defense_evasion_dns_over_https_enabled.json'; -import rule588 from './defense_evasion_whitespace_padding_in_command_line.json'; -import rule589 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; -import rule590 from './credential_access_azure_full_network_packet_capture_detected.json'; -import rule591 from './persistence_webshell_detection.json'; -import rule592 from './defense_evasion_suppression_rule_created.json'; -import rule593 from './impact_efs_filesystem_or_mount_deleted.json'; -import rule594 from './defense_evasion_execution_control_panel_suspicious_args.json'; -import rule595 from './defense_evasion_azure_blob_permissions_modified.json'; -import rule596 from './privilege_escalation_aws_suspicious_saml_activity.json'; -import rule597 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; -import rule598 from './discovery_virtual_machine_fingerprinting_grep.json'; -import rule599 from './impact_backup_file_deletion.json'; -import rule600 from './credential_access_posh_minidump.json'; -import rule601 from './persistence_screensaver_engine_unexpected_child_process.json'; -import rule602 from './persistence_screensaver_plist_file_modification.json'; -import rule603 from './credential_access_suspicious_lsass_access_memdump.json'; -import rule604 from './defense_evasion_suspicious_process_access_direct_syscall.json'; -import rule605 from './discovery_posh_suspicious_api_functions.json'; -import rule606 from './privilege_escalation_via_rogue_named_pipe.json'; -import rule607 from './credential_access_suspicious_lsass_access_via_snapshot.json'; -import rule608 from './defense_evasion_posh_process_injection.json'; -import rule609 from './collection_posh_keylogger.json'; -import rule610 from './defense_evasion_posh_assembly_load.json'; -import rule611 from './defense_evasion_powershell_windows_firewall_disabled.json'; -import rule612 from './execution_posh_portable_executable.json'; -import rule613 from './execution_posh_psreflect.json'; -import rule614 from './credential_access_suspicious_comsvcs_imageload.json'; -import rule615 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; -import rule616 from './defense_evasion_microsoft_defender_tampering.json'; -import rule617 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; -import rule618 from './persistence_remote_password_reset.json'; -import rule619 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; -import rule620 from './collection_posh_audio_capture.json'; -import rule621 from './collection_posh_screen_grabber.json'; -import rule622 from './defense_evasion_posh_compressed.json'; -import rule623 from './defense_evasion_suspicious_process_creation_calltrace.json'; -import rule624 from './privilege_escalation_group_policy_iniscript.json'; -import rule625 from './privilege_escalation_group_policy_privileged_groups.json'; -import rule626 from './privilege_escalation_group_policy_scheduled_task.json'; -import rule627 from './defense_evasion_clearing_windows_console_history.json'; -import rule628 from './threat_intel_filebeat8x.json'; -import rule629 from './privilege_escalation_installertakeover.json'; -import rule630 from './credential_access_via_snapshot_lsass_clone_creation.json'; -import rule631 from './persistence_via_bits_job_notify_command.json'; -import rule632 from './execution_suspicious_java_netcon_childproc.json'; -import rule633 from './privilege_escalation_samaccountname_spoofing_attack.json'; -import rule634 from './credential_access_symbolic_link_to_shadow_copy_created.json'; -import rule635 from './credential_access_mfa_push_brute_force.json'; -import rule636 from './persistence_azure_global_administrator_role_assigned.json'; -import rule637 from './persistence_microsoft_365_global_administrator_role_assign.json'; -import rule638 from './lateral_movement_malware_uploaded_onedrive.json'; -import rule639 from './lateral_movement_malware_uploaded_sharepoint.json'; -import rule640 from './defense_evasion_ms_office_suspicious_regmod.json'; -import rule641 from './initial_access_o365_user_reported_phish_malware.json'; -import rule642 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; -import rule643 from './credential_access_disable_kerberos_preauth.json'; -import rule644 from './credential_access_posh_request_ticket.json'; -import rule645 from './credential_access_shadow_credentials.json'; -import rule646 from './privilege_escalation_pkexec_envar_hijack.json'; -import rule647 from './credential_access_seenabledelegationprivilege_assigned_to_user.json'; -import rule648 from './persistence_msds_alloweddelegateto_krbtgt.json'; -import rule649 from './defense_evasion_disable_posh_scriptblocklogging.json'; -import rule650 from './persistence_ad_adminsdholder.json'; -import rule651 from './privilege_escalation_windows_service_via_unusual_client.json'; -import rule652 from './credential_access_dcsync_replication_rights.json'; -import rule653 from './credential_access_lsass_memdump_handle_access.json'; -import rule654 from './credential_access_moving_registry_hive_via_smb.json'; -import rule655 from './credential_access_suspicious_winreg_access_via_sebackup_priv.json'; -import rule656 from './credential_access_spn_attribute_modified.json'; -import rule657 from './persistence_dontexpirepasswd_account.json'; -import rule658 from './execution_apt_binary.json'; -import rule659 from './execution_awk_binary_shell.json'; -import rule660 from './execution_env_binary.json'; -import rule661 from './persistence_sdprop_exclusion_dsheuristics.json'; -import rule662 from './execution_find_binary.json'; -import rule663 from './credential_access_remote_sam_secretsdump.json'; -import rule664 from './defense_evasion_workfolders_control_execution.json'; -import rule665 from './execution_vi_binary.json'; -import rule666 from './execution_expect_binary.json'; -import rule667 from './execution_gcc_binary.json'; -import rule668 from './execution_mysql_binary.json'; -import rule669 from './execution_ssh_binary.json'; -import rule670 from './execution_busybox_binary.json'; -import rule671 from './execution_c89_c99_binary.json'; -import rule672 from './execution_cpulimit_binary.json'; -import rule673 from './execution_crash_binary.json'; -import rule674 from './credential_access_user_impersonation_access.json'; -import rule675 from './execution_flock_binary.json'; +import rule101 from './ml_linux_anomalous_process_all_hosts.json'; +import rule102 from './ml_linux_anomalous_user_name.json'; +import rule103 from './ml_packetbeat_dns_tunneling.json'; +import rule104 from './ml_packetbeat_rare_dns_question.json'; +import rule105 from './ml_packetbeat_rare_server_domain.json'; +import rule106 from './ml_packetbeat_rare_urls.json'; +import rule107 from './ml_packetbeat_rare_user_agent.json'; +import rule108 from './ml_rare_process_by_host_linux.json'; +import rule109 from './ml_rare_process_by_host_windows.json'; +import rule110 from './ml_suspicious_login_activity.json'; +import rule111 from './ml_windows_anomalous_network_activity.json'; +import rule112 from './ml_windows_anomalous_path_activity.json'; +import rule113 from './ml_windows_anomalous_process_all_hosts.json'; +import rule114 from './ml_windows_anomalous_process_creation.json'; +import rule115 from './ml_windows_anomalous_script.json'; +import rule116 from './ml_windows_anomalous_service.json'; +import rule117 from './ml_windows_anomalous_user_name.json'; +import rule118 from './ml_windows_rare_user_runas_event.json'; +import rule119 from './ml_windows_rare_user_type10_remote_login.json'; +import rule120 from './execution_suspicious_pdf_reader.json'; +import rule121 from './privilege_escalation_sudoers_file_mod.json'; +import rule122 from './defense_evasion_iis_httplogging_disabled.json'; +import rule123 from './execution_python_tty_shell.json'; +import rule124 from './execution_perl_tty_shell.json'; +import rule125 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; +import rule126 from './defense_evasion_file_mod_writable_dir.json'; +import rule127 from './defense_evasion_disable_selinux_attempt.json'; +import rule128 from './discovery_kernel_module_enumeration.json'; +import rule129 from './lateral_movement_telnet_network_activity_external.json'; +import rule130 from './lateral_movement_telnet_network_activity_internal.json'; +import rule131 from './privilege_escalation_setuid_setgid_bit_set_via_chmod.json'; +import rule132 from './defense_evasion_attempt_to_disable_iptables_or_firewall.json'; +import rule133 from './defense_evasion_kernel_module_removal.json'; +import rule134 from './defense_evasion_attempt_to_disable_syslog_service.json'; +import rule135 from './defense_evasion_file_deletion_via_shred.json'; +import rule136 from './discovery_virtual_machine_fingerprinting.json'; +import rule137 from './defense_evasion_hidden_file_dir_tmp.json'; +import rule138 from './defense_evasion_deletion_of_bash_command_line_history.json'; +import rule139 from './impact_cloudwatch_log_group_deletion.json'; +import rule140 from './impact_cloudwatch_log_stream_deletion.json'; +import rule141 from './impact_rds_instance_cluster_stoppage.json'; +import rule142 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; +import rule143 from './persistence_rds_cluster_creation.json'; +import rule144 from './credential_access_attempted_bypass_of_okta_mfa.json'; +import rule145 from './defense_evasion_waf_acl_deletion.json'; +import rule146 from './impact_attempt_to_revoke_okta_api_token.json'; +import rule147 from './impact_iam_group_deletion.json'; +import rule148 from './impact_possible_okta_dos_attack.json'; +import rule149 from './impact_rds_instance_cluster_deletion.json'; +import rule150 from './initial_access_suspicious_activity_reported_by_okta_user.json'; +import rule151 from './okta_attempt_to_deactivate_okta_policy.json'; +import rule152 from './okta_attempt_to_deactivate_okta_policy_rule.json'; +import rule153 from './okta_attempt_to_modify_okta_network_zone.json'; +import rule154 from './okta_attempt_to_modify_okta_policy.json'; +import rule155 from './okta_attempt_to_modify_okta_policy_rule.json'; +import rule156 from './okta_threat_detected_by_okta_threatinsight.json'; +import rule157 from './persistence_administrator_privileges_assigned_to_okta_group.json'; +import rule158 from './persistence_attempt_to_create_okta_api_token.json'; +import rule159 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; +import rule160 from './defense_evasion_cloudtrail_logging_deleted.json'; +import rule161 from './defense_evasion_ec2_network_acl_deletion.json'; +import rule162 from './impact_iam_deactivate_mfa_device.json'; +import rule163 from './defense_evasion_s3_bucket_configuration_deletion.json'; +import rule164 from './defense_evasion_guardduty_detector_deletion.json'; +import rule165 from './okta_attempt_to_delete_okta_policy.json'; +import rule166 from './credential_access_iam_user_addition_to_group.json'; +import rule167 from './persistence_ec2_network_acl_creation.json'; +import rule168 from './impact_ec2_disable_ebs_encryption.json'; +import rule169 from './persistence_iam_group_creation.json'; +import rule170 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; +import rule171 from './collection_cloudtrail_logging_created.json'; +import rule172 from './defense_evasion_cloudtrail_logging_suspended.json'; +import rule173 from './impact_cloudtrail_logging_updated.json'; +import rule174 from './initial_access_console_login_root.json'; +import rule175 from './defense_evasion_cloudwatch_alarm_deletion.json'; +import rule176 from './defense_evasion_ec2_flow_log_deletion.json'; +import rule177 from './defense_evasion_configuration_recorder_stopped.json'; +import rule178 from './exfiltration_ec2_snapshot_change_activity.json'; +import rule179 from './defense_evasion_config_service_rule_deletion.json'; +import rule180 from './okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; +import rule181 from './command_and_control_download_rar_powershell_from_internet.json'; +import rule182 from './initial_access_password_recovery.json'; +import rule183 from './command_and_control_cobalt_strike_beacon.json'; +import rule184 from './command_and_control_fin7_c2_behavior.json'; +import rule185 from './command_and_control_halfbaked_beacon.json'; +import rule186 from './credential_access_secretsmanager_getsecretvalue.json'; +import rule187 from './initial_access_via_system_manager.json'; +import rule188 from './privilege_escalation_root_login_without_mfa.json'; +import rule189 from './privilege_escalation_updateassumerolepolicy.json'; +import rule190 from './impact_hosts_file_modified.json'; +import rule191 from './elastic_endpoint_security.json'; +import rule192 from './external_alerts.json'; +import rule193 from './initial_access_login_failures.json'; +import rule194 from './initial_access_login_location.json'; +import rule195 from './initial_access_login_sessions.json'; +import rule196 from './initial_access_login_time.json'; +import rule197 from './ml_cloudtrail_error_message_spike.json'; +import rule198 from './ml_cloudtrail_rare_error_code.json'; +import rule199 from './ml_cloudtrail_rare_method_by_city.json'; +import rule200 from './ml_cloudtrail_rare_method_by_country.json'; +import rule201 from './ml_cloudtrail_rare_method_by_user.json'; +import rule202 from './credential_access_aws_iam_assume_role_brute_force.json'; +import rule203 from './credential_access_okta_brute_force_or_password_spraying.json'; +import rule204 from './initial_access_unusual_dns_service_children.json'; +import rule205 from './initial_access_unusual_dns_service_file_writes.json'; +import rule206 from './lateral_movement_dns_server_overflow.json'; +import rule207 from './credential_access_root_console_failure_brute_force.json'; +import rule208 from './initial_access_unsecure_elasticsearch_node.json'; +import rule209 from './impact_virtual_network_device_modified.json'; +import rule210 from './credential_access_domain_backup_dpapi_private_keys.json'; +import rule211 from './persistence_gpo_schtask_service_creation.json'; +import rule212 from './credential_access_credentials_keychains.json'; +import rule213 from './credential_access_kerberosdump_kcc.json'; +import rule214 from './defense_evasion_attempt_del_quarantine_attrib.json'; +import rule215 from './execution_suspicious_psexesvc.json'; +import rule216 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; +import rule217 from './privilege_escalation_printspooler_service_suspicious_file.json'; +import rule218 from './privilege_escalation_printspooler_suspicious_spl_file.json'; +import rule219 from './defense_evasion_azure_diagnostic_settings_deletion.json'; +import rule220 from './execution_command_virtual_machine.json'; +import rule221 from './execution_via_hidden_shell_conhost.json'; +import rule222 from './impact_resource_group_deletion.json'; +import rule223 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; +import rule224 from './persistence_via_update_orchestrator_service_hijack.json'; +import rule225 from './collection_update_event_hub_auth_rule.json'; +import rule226 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; +import rule227 from './credential_access_iis_connectionstrings_dumping.json'; +import rule228 from './defense_evasion_event_hub_deletion.json'; +import rule229 from './defense_evasion_firewall_policy_deletion.json'; +import rule230 from './defense_evasion_sdelete_like_filename_rename.json'; +import rule231 from './lateral_movement_remote_ssh_login_enabled.json'; +import rule232 from './persistence_azure_automation_account_created.json'; +import rule233 from './persistence_azure_automation_runbook_created_or_modified.json'; +import rule234 from './persistence_azure_automation_webhook_created.json'; +import rule235 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; +import rule236 from './credential_access_attempts_to_brute_force_okta_user_account.json'; +import rule237 from './credential_access_storage_account_key_regenerated.json'; +import rule238 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; +import rule239 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; +import rule240 from './defense_evasion_unusual_system_vp_child_program.json'; +import rule241 from './discovery_blob_container_access_mod.json'; +import rule242 from './persistence_mfa_disabled_for_azure_user.json'; +import rule243 from './persistence_user_added_as_owner_for_azure_application.json'; +import rule244 from './persistence_user_added_as_owner_for_azure_service_principal.json'; +import rule245 from './defense_evasion_dotnet_compiler_parent_process.json'; +import rule246 from './defense_evasion_suspicious_managedcode_host_process.json'; +import rule247 from './execution_command_shell_started_by_unusual_process.json'; +import rule248 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; +import rule249 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; +import rule250 from './defense_evasion_masquerading_werfault.json'; +import rule251 from './credential_access_key_vault_modified.json'; +import rule252 from './credential_access_mimikatz_memssp_default_logs.json'; +import rule253 from './defense_evasion_code_injection_conhost.json'; +import rule254 from './defense_evasion_network_watcher_deletion.json'; +import rule255 from './initial_access_external_guest_user_invite.json'; +import rule256 from './defense_evasion_masquerading_renamed_autoit.json'; +import rule257 from './impact_azure_automation_runbook_deleted.json'; +import rule258 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; +import rule259 from './persistence_azure_conditional_access_policy_modified.json'; +import rule260 from './persistence_azure_privileged_identity_management_role_modified.json'; +import rule261 from './command_and_control_teamviewer_remote_file_copy.json'; +import rule262 from './defense_evasion_installutil_beacon.json'; +import rule263 from './defense_evasion_mshta_beacon.json'; +import rule264 from './defense_evasion_network_connection_from_windows_binary.json'; +import rule265 from './defense_evasion_rundll32_no_arguments.json'; +import rule266 from './defense_evasion_suspicious_scrobj_load.json'; +import rule267 from './defense_evasion_suspicious_wmi_script.json'; +import rule268 from './execution_ms_office_written_file.json'; +import rule269 from './execution_pdf_written_file.json'; +import rule270 from './lateral_movement_cmd_service.json'; +import rule271 from './persistence_app_compat_shim.json'; +import rule272 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; +import rule273 from './command_and_control_remote_file_copy_mpcmdrun.json'; +import rule274 from './defense_evasion_execution_suspicious_explorer_winword.json'; +import rule275 from './defense_evasion_suspicious_zoom_child_process.json'; +import rule276 from './ml_linux_anomalous_compiler_activity.json'; +import rule277 from './ml_linux_anomalous_sudo_activity.json'; +import rule278 from './ml_linux_system_information_discovery.json'; +import rule279 from './ml_linux_system_network_configuration_discovery.json'; +import rule280 from './ml_linux_system_network_connection_discovery.json'; +import rule281 from './ml_linux_system_process_discovery.json'; +import rule282 from './ml_linux_system_user_discovery.json'; +import rule283 from './discovery_post_exploitation_external_ip_lookup.json'; +import rule284 from './initial_access_zoom_meeting_with_no_passcode.json'; +import rule285 from './defense_evasion_gcp_logging_sink_deletion.json'; +import rule286 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; +import rule287 from './defense_evasion_gcp_firewall_rule_created.json'; +import rule288 from './defense_evasion_gcp_firewall_rule_deleted.json'; +import rule289 from './defense_evasion_gcp_firewall_rule_modified.json'; +import rule290 from './defense_evasion_gcp_logging_bucket_deletion.json'; +import rule291 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; +import rule292 from './impact_gcp_storage_bucket_deleted.json'; +import rule293 from './initial_access_gcp_iam_custom_role_creation.json'; +import rule294 from './persistence_gcp_iam_service_account_key_deletion.json'; +import rule295 from './persistence_gcp_key_created_for_service_account.json'; +import rule296 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; +import rule297 from './exfiltration_gcp_logging_sink_modification.json'; +import rule298 from './impact_gcp_iam_role_deletion.json'; +import rule299 from './impact_gcp_service_account_deleted.json'; +import rule300 from './impact_gcp_service_account_disabled.json'; +import rule301 from './impact_gcp_virtual_private_cloud_network_deleted.json'; +import rule302 from './impact_gcp_virtual_private_cloud_route_created.json'; +import rule303 from './impact_gcp_virtual_private_cloud_route_deleted.json'; +import rule304 from './ml_linux_anomalous_metadata_process.json'; +import rule305 from './ml_linux_anomalous_metadata_user.json'; +import rule306 from './ml_windows_anomalous_metadata_process.json'; +import rule307 from './ml_windows_anomalous_metadata_user.json'; +import rule308 from './persistence_gcp_service_account_created.json'; +import rule309 from './collection_gcp_pub_sub_subscription_creation.json'; +import rule310 from './collection_gcp_pub_sub_topic_creation.json'; +import rule311 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; +import rule312 from './persistence_azure_pim_user_added_global_admin.json'; +import rule313 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; +import rule314 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; +import rule315 from './defense_evasion_execution_lolbas_wuauclt.json'; +import rule316 from './privilege_escalation_unusual_svchost_childproc_childless.json'; +import rule317 from './command_and_control_rdp_tunnel_plink.json'; +import rule318 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; +import rule319 from './discovery_privileged_localgroup_membership.json'; +import rule320 from './persistence_ms_office_addins_file.json'; +import rule321 from './discovery_adfind_command_activity.json'; +import rule322 from './discovery_security_software_wmic.json'; +import rule323 from './execution_command_shell_via_rundll32.json'; +import rule324 from './execution_suspicious_cmd_wmi.json'; +import rule325 from './lateral_movement_via_startup_folder_rdp_smb.json'; +import rule326 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; +import rule327 from './privilege_escalation_uac_bypass_mock_windir.json'; +import rule328 from './defense_evasion_potential_processherpaderping.json'; +import rule329 from './privilege_escalation_uac_bypass_dll_sideloading.json'; +import rule330 from './execution_shared_modules_local_sxs_dll.json'; +import rule331 from './privilege_escalation_uac_bypass_com_clipup.json'; +import rule332 from './initial_access_via_explorer_suspicious_child_parent_args.json'; +import rule333 from './execution_from_unusual_directory.json'; +import rule334 from './execution_from_unusual_path_cmdline.json'; +import rule335 from './credential_access_kerberoasting_unusual_process.json'; +import rule336 from './discovery_peripheral_device.json'; +import rule337 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; +import rule338 from './defense_evasion_deleting_websvr_access_logs.json'; +import rule339 from './defense_evasion_log_files_deleted.json'; +import rule340 from './defense_evasion_timestomp_touch.json'; +import rule341 from './lateral_movement_dcom_hta.json'; +import rule342 from './lateral_movement_execution_via_file_shares_sequence.json'; +import rule343 from './privilege_escalation_uac_bypass_com_ieinstal.json'; +import rule344 from './command_and_control_common_webservices.json'; +import rule345 from './command_and_control_encrypted_channel_freesslcert.json'; +import rule346 from './defense_evasion_process_termination_followed_by_deletion.json'; +import rule347 from './lateral_movement_remote_file_copy_hidden_share.json'; +import rule348 from './attempt_to_deactivate_okta_network_zone.json'; +import rule349 from './attempt_to_delete_okta_network_zone.json'; +import rule350 from './lateral_movement_dcom_mmc20.json'; +import rule351 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; +import rule352 from './okta_attempt_to_deactivate_okta_application.json'; +import rule353 from './okta_attempt_to_delete_okta_application.json'; +import rule354 from './okta_attempt_to_delete_okta_policy_rule.json'; +import rule355 from './okta_attempt_to_modify_okta_application.json'; +import rule356 from './persistence_administrator_role_assigned_to_okta_user.json'; +import rule357 from './lateral_movement_executable_tool_transfer_smb.json'; +import rule358 from './command_and_control_dns_tunneling_nslookup.json'; +import rule359 from './lateral_movement_execution_from_tsclient_mup.json'; +import rule360 from './lateral_movement_rdp_sharprdp_target.json'; +import rule361 from './defense_evasion_clearing_windows_security_logs.json'; +import rule362 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; +import rule363 from './execution_suspicious_short_program_name.json'; +import rule364 from './lateral_movement_incoming_wmi.json'; +import rule365 from './persistence_via_hidden_run_key_valuename.json'; +import rule366 from './credential_access_potential_ssh_bruteforce.json'; +import rule367 from './credential_access_promt_for_pwd_via_osascript.json'; +import rule368 from './lateral_movement_remote_services.json'; +import rule369 from './application_added_to_google_workspace_domain.json'; +import rule370 from './domain_added_to_google_workspace_trusted_domains.json'; +import rule371 from './execution_suspicious_image_load_wmi_ms_office.json'; +import rule372 from './execution_suspicious_powershell_imgload.json'; +import rule373 from './google_workspace_admin_role_deletion.json'; +import rule374 from './google_workspace_mfa_enforcement_disabled.json'; +import rule375 from './google_workspace_policy_modified.json'; +import rule376 from './mfa_disabled_for_google_workspace_organization.json'; +import rule377 from './persistence_evasion_registry_ifeo_injection.json'; +import rule378 from './persistence_google_workspace_admin_role_assigned_to_user.json'; +import rule379 from './persistence_google_workspace_custom_admin_role_created.json'; +import rule380 from './persistence_google_workspace_role_modified.json'; +import rule381 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; +import rule382 from './defense_evasion_masquerading_trusted_directory.json'; +import rule383 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; +import rule384 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; +import rule385 from './microsoft_365_exchange_dkim_signing_config_disabled.json'; +import rule386 from './persistence_appcertdlls_registry.json'; +import rule387 from './persistence_appinitdlls_registry.json'; +import rule388 from './persistence_registry_uncommon.json'; +import rule389 from './persistence_run_key_and_startup_broad.json'; +import rule390 from './persistence_services_registry.json'; +import rule391 from './persistence_startup_folder_file_written_by_suspicious_process.json'; +import rule392 from './persistence_startup_folder_scripts.json'; +import rule393 from './persistence_suspicious_com_hijack_registry.json'; +import rule394 from './persistence_via_lsa_security_support_provider_registry.json'; +import rule395 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; +import rule396 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; +import rule397 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; +import rule398 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; +import rule399 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; +import rule400 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; +import rule401 from './lateral_movement_suspicious_rdp_client_imageload.json'; +import rule402 from './persistence_runtime_run_key_startup_susp_procs.json'; +import rule403 from './persistence_suspicious_scheduled_task_runtime.json'; +import rule404 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; +import rule405 from './lateral_movement_scheduled_task_target.json'; +import rule406 from './persistence_microsoft_365_exchange_management_role_assignment.json'; +import rule407 from './persistence_microsoft_365_teams_guest_access_enabled.json'; +import rule408 from './credential_access_dump_registry_hives.json'; +import rule409 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; +import rule410 from './persistence_ms_outlook_vba_template.json'; +import rule411 from './persistence_suspicious_service_created_registry.json'; +import rule412 from './privilege_escalation_named_pipe_impersonation.json'; +import rule413 from './credential_access_cmdline_dump_tool.json'; +import rule414 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; +import rule415 from './credential_access_lsass_memdump_file_created.json'; +import rule416 from './lateral_movement_incoming_winrm_shell_execution.json'; +import rule417 from './lateral_movement_powershell_remoting_target.json'; +import rule418 from './command_and_control_port_forwarding_added_registry.json'; +import rule419 from './defense_evasion_hide_encoded_executable_registry.json'; +import rule420 from './lateral_movement_rdp_enabled_registry.json'; +import rule421 from './privilege_escalation_printspooler_registry_copyfiles.json'; +import rule422 from './privilege_escalation_rogue_windir_environment_var.json'; +import rule423 from './initial_access_scripts_process_started_via_wmi.json'; +import rule424 from './command_and_control_iexplore_via_com.json'; +import rule425 from './command_and_control_remote_file_copy_scripts.json'; +import rule426 from './persistence_local_scheduled_task_scripting.json'; +import rule427 from './persistence_startup_folder_file_written_by_unsigned_process.json'; +import rule428 from './command_and_control_remote_file_copy_powershell.json'; +import rule429 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; +import rule430 from './microsoft_365_teams_custom_app_interaction_allowed.json'; +import rule431 from './persistence_microsoft_365_teams_external_access_enabled.json'; +import rule432 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; +import rule433 from './impact_stop_process_service_threshold.json'; +import rule434 from './collection_winrar_encryption.json'; +import rule435 from './defense_evasion_unusual_dir_ads.json'; +import rule436 from './discovery_admin_recon.json'; +import rule437 from './discovery_file_dir_discovery.json'; +import rule438 from './discovery_net_view.json'; +import rule439 from './discovery_remote_system_discovery_commands_windows.json'; +import rule440 from './persistence_via_windows_management_instrumentation_event_subscription.json'; +import rule441 from './credential_access_mimikatz_powershell_module.json'; +import rule442 from './execution_scripting_osascript_exec_followed_by_netcon.json'; +import rule443 from './execution_shell_execution_via_apple_scripting.json'; +import rule444 from './persistence_creation_change_launch_agents_file.json'; +import rule445 from './persistence_creation_modif_launch_deamon_sequence.json'; +import rule446 from './persistence_folder_action_scripts_runtime.json'; +import rule447 from './persistence_login_logout_hooks_defaults.json'; +import rule448 from './privilege_escalation_explicit_creds_via_scripting.json'; +import rule449 from './command_and_control_sunburst_c2_activity_detected.json'; +import rule450 from './defense_evasion_azure_application_credential_modification.json'; +import rule451 from './defense_evasion_azure_service_principal_addition.json'; +import rule452 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; +import rule453 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; +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 './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'; +import rule462 from './discovery_security_software_grep.json'; +import rule463 from './credential_access_cookies_chromium_browsers_debugging.json'; +import rule464 from './credential_access_ssh_backdoor_log.json'; +import rule465 from './persistence_credential_access_modify_auth_module_or_config.json'; +import rule466 from './persistence_credential_access_modify_ssh_binaries.json'; +import rule467 from './credential_access_collection_sensitive_files.json'; +import rule468 from './persistence_ssh_authorized_keys_modification.json'; +import rule469 from './defense_evasion_defender_disabled_via_registry.json'; +import rule470 from './defense_evasion_privacy_controls_tcc_database_modification.json'; +import rule471 from './execution_initial_access_suspicious_browser_childproc.json'; +import rule472 from './execution_script_via_automator_workflows.json'; +import rule473 from './persistence_modification_sublime_app_plugin_or_script.json'; +import rule474 from './privilege_escalation_applescript_with_admin_privs.json'; +import rule475 from './credential_access_dumping_keychain_security.json'; +import rule476 from './initial_access_azure_active_directory_high_risk_signin.json'; +import rule477 from './initial_access_suspicious_mac_ms_office_child_process.json'; +import rule478 from './credential_access_mitm_localhost_webproxy.json'; +import rule479 from './persistence_kde_autostart_modification.json'; +import rule480 from './persistence_user_account_added_to_privileged_group_ad.json'; +import rule481 from './defense_evasion_attempt_to_disable_gatekeeper.json'; +import rule482 from './defense_evasion_sandboxed_office_app_suspicious_zip_file.json'; +import rule483 from './persistence_emond_rules_file_creation.json'; +import rule484 from './persistence_emond_rules_process_execution.json'; +import rule485 from './discovery_users_domain_built_in_commands.json'; +import rule486 from './execution_pentest_eggshell_remote_admin_tool.json'; +import rule487 from './defense_evasion_install_root_certificate.json'; +import rule488 from './persistence_credential_access_authorization_plugin_creation.json'; +import rule489 from './persistence_directory_services_plugins_modification.json'; +import rule490 from './defense_evasion_modify_environment_launchctl.json'; +import rule491 from './defense_evasion_safari_config_change.json'; +import rule492 from './defense_evasion_apple_softupdates_modification.json'; +import rule493 from './credential_access_mod_wdigest_security_provider.json'; +import rule494 from './credential_access_saved_creds_vaultcmd.json'; +import rule495 from './defense_evasion_file_creation_mult_extension.json'; +import rule496 from './execution_enumeration_via_wmiprvse.json'; +import rule497 from './execution_suspicious_jar_child_process.json'; +import rule498 from './persistence_shell_profile_modification.json'; +import rule499 from './persistence_suspicious_calendar_modification.json'; +import rule500 from './persistence_time_provider_mod.json'; +import rule501 from './privilege_escalation_exploit_adobe_acrobat_updater.json'; +import rule502 from './defense_evasion_sip_provider_mod.json'; +import rule503 from './execution_com_object_xwizard.json'; +import rule504 from './privilege_escalation_disable_uac_registry.json'; +import rule505 from './defense_evasion_unusual_ads_file_creation.json'; +import rule506 from './persistence_loginwindow_plist_modification.json'; +import rule507 from './persistence_periodic_tasks_file_mdofiy.json'; +import rule508 from './persistence_via_atom_init_file_modification.json'; +import rule509 from './privilege_escalation_lsa_auth_package.json'; +import rule510 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; +import rule511 from './credential_access_dumping_hashes_bi_cmds.json'; +import rule512 from './lateral_movement_mounting_smb_share.json'; +import rule513 from './privilege_escalation_echo_nopasswd_sudoers.json'; +import rule514 from './privilege_escalation_ld_preload_shared_object_modif.json'; +import rule515 from './privilege_escalation_root_crontab_filemod.json'; +import rule516 from './defense_evasion_create_mod_root_certificate.json'; +import rule517 from './privilege_escalation_sudo_buffer_overflow.json'; +import rule518 from './execution_installer_spawned_network_event.json'; +import rule519 from './initial_access_suspicious_ms_exchange_files.json'; +import rule520 from './initial_access_suspicious_ms_exchange_process.json'; +import rule521 from './initial_access_suspicious_ms_exchange_worker_child_process.json'; +import rule522 from './persistence_evasion_registry_startup_shell_folder_modified.json'; +import rule523 from './persistence_local_scheduled_job_creation.json'; +import rule524 from './persistence_via_wmi_stdregprov_run_services.json'; +import rule525 from './credential_access_persistence_network_logon_provider_modification.json'; +import rule526 from './lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json'; +import rule527 from './collection_microsoft_365_new_inbox_rule.json'; +import rule528 from './ml_high_count_network_denies.json'; +import rule529 from './ml_high_count_network_events.json'; +import rule530 from './ml_rare_destination_country.json'; +import rule531 from './ml_spike_in_traffic_to_a_country.json'; +import rule532 from './command_and_control_tunneling_via_earthworm.json'; +import rule533 from './lateral_movement_evasion_rdp_shadowing.json'; +import rule534 from './threat_intel_fleet_integrations.json'; +import rule535 from './exfiltration_ec2_vm_export_failure.json'; +import rule536 from './exfiltration_ec2_full_network_packet_capture_detected.json'; +import rule537 from './impact_azure_service_principal_credentials_added.json'; +import rule538 from './persistence_ec2_security_group_configuration_change_detection.json'; +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 './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_assumerole_usage.json'; +import rule547 from './privilege_escalation_sts_getsessiontoken_abuse.json'; +import rule548 from './defense_evasion_suspicious_execution_from_mounted_device.json'; +import rule549 from './defense_evasion_unusual_network_connection_via_dllhost.json'; +import rule550 from './defense_evasion_amsienable_key_mod.json'; +import rule551 from './impact_rds_group_deletion.json'; +import rule552 from './persistence_rds_group_creation.json'; +import rule553 from './persistence_route_table_created.json'; +import rule554 from './persistence_route_table_modified_or_deleted.json'; +import rule555 from './exfiltration_rds_snapshot_export.json'; +import rule556 from './persistence_rds_instance_creation.json'; +import rule557 from './privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json'; +import rule558 from './ml_auth_rare_hour_for_a_user_to_logon.json'; +import rule559 from './ml_auth_rare_source_ip_for_a_user.json'; +import rule560 from './ml_auth_rare_user_logon.json'; +import rule561 from './ml_auth_spike_in_failed_logon_events.json'; +import rule562 from './ml_auth_spike_in_logon_events.json'; +import rule563 from './ml_auth_spike_in_logon_events_from_a_source_ip.json'; +import rule564 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; +import rule565 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; +import rule566 from './defense_evasion_kubernetes_events_deleted.json'; +import rule567 from './impact_kubernetes_pod_deleted.json'; +import rule568 from './exfiltration_rds_snapshot_restored.json'; +import rule569 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; +import rule570 from './privilege_escalation_unusual_printspooler_childprocess.json'; +import rule571 from './defense_evasion_disabling_windows_defender_powershell.json'; +import rule572 from './defense_evasion_enable_network_discovery_with_netsh.json'; +import rule573 from './defense_evasion_execution_windefend_unusual_path.json'; +import rule574 from './defense_evasion_agent_spoofing_mismatched_id.json'; +import rule575 from './defense_evasion_agent_spoofing_multiple_hosts.json'; +import rule576 from './defense_evasion_parent_process_pid_spoofing.json'; +import rule577 from './impact_microsoft_365_potential_ransomware_activity.json'; +import rule578 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; +import rule579 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; +import rule580 from './defense_evasion_elasticache_security_group_creation.json'; +import rule581 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; +import rule582 from './impact_volume_shadow_copy_deletion_via_powershell.json'; +import rule583 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; +import rule584 from './defense_evasion_defender_exclusion_via_powershell.json'; +import rule585 from './defense_evasion_dns_over_https_enabled.json'; +import rule586 from './defense_evasion_whitespace_padding_in_command_line.json'; +import rule587 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; +import rule588 from './credential_access_azure_full_network_packet_capture_detected.json'; +import rule589 from './persistence_webshell_detection.json'; +import rule590 from './defense_evasion_suppression_rule_created.json'; +import rule591 from './impact_efs_filesystem_or_mount_deleted.json'; +import rule592 from './defense_evasion_execution_control_panel_suspicious_args.json'; +import rule593 from './defense_evasion_azure_blob_permissions_modified.json'; +import rule594 from './privilege_escalation_aws_suspicious_saml_activity.json'; +import rule595 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; +import rule596 from './discovery_virtual_machine_fingerprinting_grep.json'; +import rule597 from './impact_backup_file_deletion.json'; +import rule598 from './credential_access_posh_minidump.json'; +import rule599 from './persistence_screensaver_engine_unexpected_child_process.json'; +import rule600 from './persistence_screensaver_plist_file_modification.json'; +import rule601 from './credential_access_suspicious_lsass_access_memdump.json'; +import rule602 from './defense_evasion_suspicious_process_access_direct_syscall.json'; +import rule603 from './discovery_posh_suspicious_api_functions.json'; +import rule604 from './privilege_escalation_via_rogue_named_pipe.json'; +import rule605 from './credential_access_suspicious_lsass_access_via_snapshot.json'; +import rule606 from './defense_evasion_posh_process_injection.json'; +import rule607 from './collection_posh_keylogger.json'; +import rule608 from './defense_evasion_posh_assembly_load.json'; +import rule609 from './defense_evasion_powershell_windows_firewall_disabled.json'; +import rule610 from './execution_posh_portable_executable.json'; +import rule611 from './execution_posh_psreflect.json'; +import rule612 from './credential_access_suspicious_comsvcs_imageload.json'; +import rule613 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; +import rule614 from './defense_evasion_microsoft_defender_tampering.json'; +import rule615 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; +import rule616 from './persistence_remote_password_reset.json'; +import rule617 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; +import rule618 from './collection_posh_audio_capture.json'; +import rule619 from './collection_posh_screen_grabber.json'; +import rule620 from './defense_evasion_posh_compressed.json'; +import rule621 from './defense_evasion_suspicious_process_creation_calltrace.json'; +import rule622 from './privilege_escalation_group_policy_iniscript.json'; +import rule623 from './privilege_escalation_group_policy_privileged_groups.json'; +import rule624 from './privilege_escalation_group_policy_scheduled_task.json'; +import rule625 from './defense_evasion_clearing_windows_console_history.json'; +import rule626 from './threat_intel_filebeat8x.json'; +import rule627 from './privilege_escalation_installertakeover.json'; +import rule628 from './credential_access_via_snapshot_lsass_clone_creation.json'; +import rule629 from './persistence_via_bits_job_notify_command.json'; +import rule630 from './execution_suspicious_java_netcon_childproc.json'; +import rule631 from './privilege_escalation_samaccountname_spoofing_attack.json'; +import rule632 from './credential_access_symbolic_link_to_shadow_copy_created.json'; +import rule633 from './credential_access_mfa_push_brute_force.json'; +import rule634 from './persistence_azure_global_administrator_role_assigned.json'; +import rule635 from './persistence_microsoft_365_global_administrator_role_assign.json'; +import rule636 from './lateral_movement_malware_uploaded_onedrive.json'; +import rule637 from './lateral_movement_malware_uploaded_sharepoint.json'; +import rule638 from './defense_evasion_ms_office_suspicious_regmod.json'; +import rule639 from './initial_access_o365_user_reported_phish_malware.json'; +import rule640 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; +import rule641 from './credential_access_disable_kerberos_preauth.json'; +import rule642 from './credential_access_posh_request_ticket.json'; +import rule643 from './credential_access_shadow_credentials.json'; +import rule644 from './privilege_escalation_pkexec_envar_hijack.json'; +import rule645 from './credential_access_seenabledelegationprivilege_assigned_to_user.json'; +import rule646 from './persistence_msds_alloweddelegateto_krbtgt.json'; +import rule647 from './defense_evasion_disable_posh_scriptblocklogging.json'; +import rule648 from './persistence_ad_adminsdholder.json'; +import rule649 from './privilege_escalation_windows_service_via_unusual_client.json'; +import rule650 from './credential_access_dcsync_replication_rights.json'; +import rule651 from './credential_access_lsass_memdump_handle_access.json'; +import rule652 from './credential_access_moving_registry_hive_via_smb.json'; +import rule653 from './credential_access_suspicious_winreg_access_via_sebackup_priv.json'; +import rule654 from './credential_access_spn_attribute_modified.json'; +import rule655 from './persistence_dontexpirepasswd_account.json'; +import rule656 from './persistence_sdprop_exclusion_dsheuristics.json'; +import rule657 from './credential_access_remote_sam_secretsdump.json'; +import rule658 from './defense_evasion_workfolders_control_execution.json'; +import rule659 from './credential_access_user_impersonation_access.json'; +import rule660 from './persistence_redshift_instance_creation.json'; +import rule661 from './persistence_crontab_creation.json'; +import rule662 from './privilege_escalation_krbrelayup_suspicious_logon.json'; +import rule663 from './privilege_escalation_krbrelayup_service_creation.json'; +import rule664 from './credential_access_relay_ntlm_auth_via_http_spoolss.json'; +import rule665 from './execution_shell_evasion_linux_binary.json'; +import rule666 from './execution_process_started_in_shared_memory_directory.json'; +import rule667 from './execution_abnormal_process_id_file_created.json'; +import rule668 from './execution_process_started_from_process_id_file.json'; +import rule669 from './privilege_escalation_suspicious_dnshostname_update.json'; +import rule670 from './command_and_control_connection_attempt_by_non_ssh_root_session.json'; +import rule671 from './defense_evasion_elastic_agent_service_terminated.json'; +import rule672 from './defense_evasion_proxy_execution_via_msdt.json'; export const rawRules = [ rule1, @@ -1359,7 +1356,4 @@ export const rawRules = [ rule670, rule671, rule672, - rule673, - rule674, - rule675, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json index 9d0315ea692fb..dd5f00a4a3e35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json @@ -18,7 +18,7 @@ "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:signin.amazonaws.com and event.action:PasswordRecoveryRequested and event.outcome:success\n", "references": [ - "https://www.cadosecurity.com/2020/06/11/an-ongoing-aws-phishing-campaign/" + "https://www.cadosecurity.com/an-ongoing-aws-phishing-campaign/" ], "risk_score": 21, "rule_id": "69c420e8-6c9e-4d28-86c0-8a2be2d1e78c", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json index dcd4084fb0aac..58e985b001f5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Windows Script Executing PowerShell", - "note": "## Triage and analysis\n\n### Investigating Windows Script Executing PowerShell\n\nThe Windows Script Host (WSH) is an Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for the spawn of the `powershell.exe` process with `cscript.exe` or `wscript.exe` as its parent process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Investigate commands executed by the spawned PowerShell process.\n- Retrieve the script file(s) involved:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Manually analyze the script to determine if malicious capabilities are present.\n- Determine how the script file was delivered (email attachment, dropped by other processes, etc.).\n- Investigate other alerts related to the user/host in the last 48 hours.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the identified indicators of compromise (IoCs).\n- Remove and block malicious artifacts identified on the triage.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n- Reimage the host operating system and restore compromised files to clean versions.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Windows Script Executing PowerShell\n\nThe Windows Script Host (WSH) is an Windows automation technology, which is ideal for non-interactive scripting needs,\nsuch as logon scripting, administrative scripting, and machine automation.\n\nAttackers commonly use WSH scripts as their initial access method, acting like droppers for second stage payloads, but\ncan also use them to download tools and utilities needed to accomplish their goals.\n\nThis rule looks for the spawn of the `powershell.exe` process with `cscript.exe` or `wscript.exe` as its parent process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate commands executed by the spawned PowerShell process.\n- If unsigned files are found on the process tree, retrieve them and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Determine how the script file was delivered (email attachment, dropped by other processes, etc.).\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- The usage of these script engines by regular users is unlikely. In the case of authorized benign true positives\n(B-TPs), exceptions can be added.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : (\"cscript.exe\", \"wscript.exe\") and process.name : \"powershell.exe\"\n", "risk_score": 21, "rule_id": "f545ff26-3c94-4fd0-bd33-3c7f95a3a0fc", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 11 } 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 c4f812542c752..656199c7fa9a4 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 @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Suspicious MS Office Child Process", "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\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", + "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\", \"outlook.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\", \"msdt.exe\")\n", "risk_score": 47, "rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "severity": "medium", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 11 } 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 887b23e8018c8..a6297dba18540 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 @@ -11,7 +11,8 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Lateral Tool Transfer", + "name": "Potential Lateral Tool Transfer via SMB Share", + "note": "## Triage and analysis\n\n### Investigating Potential Lateral Tool Transfer via SMB Share\n\nAdversaries can use network shares to host tooling to support the compromise of other hosts in the environment. These tools\ncan include discovery utilities, credential dumpers, malware, etc. Attackers can also leverage file shares that employees\nfrequently access to host malicious files to gain a foothold in other machines.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve the created file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Consider adding exceptions if it is expected and noisy in your environment.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Review the privileges needed to write to the network share and restrict write access as needed.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\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\n network.transport == \"tcp\" and source.ip != \"127.0.0.1\" and source.ip != \"::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", @@ -36,10 +37,22 @@ "id": "T1570", "name": "Lateral Tool Transfer", "reference": "https://attack.mitre.org/techniques/T1570/" + }, + { + "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": 4 + "version": 5 } 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 1a8eb370e9167..a7f79d5f778bc 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "RDP Enabled via Registry", - "note": "## Triage and analysis\n\n### Investigating RDP Enabled via Registry\n\nMicrosoft Remote Desktop Protocol (RDP) is a proprietary Microsoft protocol that enables remote connections to other\ncomputers, typically over TCP port 3389.\n\nAttackers can use RDP to conduct their actions interactively. Ransomware operators frequently use RDP to access\nvictim servers, often using privileged accounts.\n\nThis rule detects modification of the fDenyTSConnections registry key to the value `0`, which specifies that remote\ndesktop connections are enabled. Attackers can abuse remote registry, use psexec, etc., to enable RDP and move laterally.\n\n#### Possible investigation steps\n\n- Identify the user account which performed the action and whether it should perform this kind of action.\n- Contact the user to check if they are aware of the operation.\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Check whether it makes sense to enable RDP to this host, given its role in the environment.\n- Check if the host is directly exposed to the internet.\n- Check whether privileged accounts accessed the host shortly after the modification.\n- Review network events within a short timespan of this alert for incoming RDP connection attempts.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user should be performing this kind of activity, whether they are aware\nof it, whether RDP should be open, and whether the action exposes the environment to unnecessary risks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If RDP is needed, make sure to secure it using firewall rules:\n - Allowlist RDP traffic to specific trusted hosts.\n - Restrict RDP logins to authorized non-administrator accounts, where possible.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Review the implicated account's privileges.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating RDP Enabled via Registry\n\nMicrosoft Remote Desktop Protocol (RDP) is a proprietary Microsoft protocol that enables remote connections to other\ncomputers, typically over TCP port 3389.\n\nAttackers can use RDP to conduct their actions interactively. Ransomware operators frequently use RDP to access\nvictim servers, often using privileged accounts.\n\nThis rule detects modification of the fDenyTSConnections registry key to the value `0`, which specifies that remote\ndesktop connections are enabled. Attackers can abuse remote registry, use psexec, etc., to enable RDP and move laterally.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user to check if they are aware of the operation.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether it makes sense to enable RDP to this host, given its role in the environment.\n- Check if the host is directly exposed to the internet.\n- Check whether privileged accounts accessed the host shortly after the modification.\n- Review network events within a short timespan of this alert for incoming RDP connection attempts.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Check whether the user should be performing this kind of activity, whether\nthey are aware of it, whether RDP should be open, and whether the action exposes the environment to unnecessary risks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If RDP is needed, make sure to secure it using firewall rules:\n - Allowlist RDP traffic to specific trusted hosts.\n - Restrict RDP logins to authorized non-administrator accounts, where possible.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\Terminal Server\\\\fDenyTSConnections\" and\n registry.data.strings : (\"0\", \"0x00000000\") and not (process.name : \"svchost.exe\" and user.domain == \"NT AUTHORITY\") and\n not process.executable : \"C:\\\\Windows\\\\System32\\\\SystemPropertiesRemote.exe\"\n", "risk_score": 47, "rule_id": "58aa72ca-d968-4f34-b9f7-bea51d75eb50", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index 8b71c053fc6d6..07dc8f663201b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -14,7 +14,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Unusual Process Execution - Temp", - "query": "event.category:process and event.type:(start or process_started) and process.working_directory:/tmp\n", + "query": "event.category:process and event.type:(start or process_started) and process.working_directory:/tmp and\n not process.parent.name:(update-motd-updates-available or\n apt or apt-* or\n cnf-update-db or\n appstreamcli or\n unattended-upgrade or\n packagekitd) and\n not process.args:(/usr/lib/update-notifier/update-motd-updates-available or\n /var/lib/command-not-found/)\n", "risk_score": 47, "rule_id": "df959768-b0c9-4d45-988c-5606a2be8e5a", "severity": "medium", @@ -26,5 +26,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/mfa_disabled_for_google_workspace_organization.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json index 87a6618d4c9d8..fb8130b9fed7a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/mfa_disabled_for_google_workspace_organization.json @@ -30,5 +30,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 11 + "version": 13 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_compiler_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_compiler_activity.json index 6f226afdd1873..90288b6127aff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_compiler_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_compiler_activity.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_rare_user_compiler", + "machine_learning_job_id": [ + "v3_linux_rare_user_compiler" + ], "name": "Anomalous Linux Compiler Activity", "risk_score": 21, "rule_id": "cd66a419-9b3f-4f57-8ff8-ac4cd2d5f530", @@ -23,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_kernel_module_arguments.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_kernel_module_arguments.json deleted file mode 100644 index c04a68171f6f8..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_kernel_module_arguments.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "anomaly_threshold": 25, - "author": [ - "Elastic" - ], - "description": "Looks for unusual kernel module activity. Kernel modules are sometimes used by malware and persistence mechanisms for stealth.", - "false_positives": [ - "A Linux host running unusual device drivers or other kinds of kernel modules could trigger this detection. Troubleshooting or debugging activity using unusual arguments could also trigger this detection." - ], - "from": "now-45m", - "interval": "15m", - "license": "Elastic License v2", - "machine_learning_job_id": "linux_rare_kernel_module_arguments", - "name": "Anomalous Kernel Module Activity", - "risk_score": 21, - "rule_id": "37b0816d-af40-40b4-885f-bb162b3c88a9", - "severity": "low", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "ML" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Persistence", - "reference": "https://attack.mitre.org/tactics/TA0003/" - }, - "technique": [ - { - "id": "T1547", - "name": "Boot or Logon Autostart Execution", - "reference": "https://attack.mitre.org/techniques/T1547/", - "subtechnique": [ - { - "id": "T1547.006", - "name": "Kernel Modules and Extensions", - "reference": "https://attack.mitre.org/techniques/T1547/006/" - } - ] - } - ] - } - ], - "type": "machine_learning", - "version": 4 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json index a2638f60d7495..c0bcf411363dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_process.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "linux_rare_metadata_process", - "v2_linux_rare_metadata_process" + "v3_linux_rare_metadata_process" ], "name": "Unusual Linux Process Calling the Metadata Service", "risk_score": 21, @@ -26,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json index c176bf0d7ad68..b1761e9c95d23 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_metadata_user.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "linux_rare_metadata_user", - "v2_linux_rare_metadata_user" + "v3_linux_rare_metadata_user" ], "name": "Unusual Linux User Calling the Metadata Service", "risk_score": 21, @@ -26,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_activity.json index 956e7f9cca592..067f926cea271 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_activity.json @@ -7,7 +7,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_anomalous_network_activity_ecs", + "machine_learning_job_id": [ + "v3_linux_anomalous_network_activity" + ], "name": "Unusual Linux Network Activity", "note": "## Triage and analysis\n\n### Investigating Unusual Network Activity\nDetection alerts from this rule indicate the presence of network activity from a Linux process for which network activity is rare and unusual. Here are some possible avenues of investigation:\n- Consider the IP addresses and ports. Are these used by normal but infrequent network workflows? Are they expected or unexpected?\n- If the destination IP address is remote or external, does it associate with an expected domain, organization or geography? Note: avoid interacting directly with suspected malicious IP addresses.\n- Consider the user as identified by the username field. Is this network activity part of an expected workflow for the user who ran the program?\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 or maintenance process.\n- Examine the process arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", "references": [ @@ -24,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_linux_anomalous_network_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json index eb6a960702ac7..a4cbfdd0d3137 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "linux_anomalous_network_port_activity_ecs", - "v2_linux_anomalous_network_port_activity_ecs" + "v3_linux_anomalous_network_port_activity" ], "name": "Unusual Linux Network Port Activity", "references": [ @@ -29,5 +28,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_service.json deleted file mode 100644 index 296674133d2e6..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_service.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "anomaly_threshold": 50, - "author": [ - "Elastic" - ], - "description": "Identifies unusual listening ports on Linux instances that can indicate execution of unauthorized services, backdoors, or persistence mechanisms.", - "false_positives": [ - "A newly installed program or one that rarely uses the network could trigger this alert." - ], - "from": "now-45m", - "interval": "15m", - "license": "Elastic License v2", - "machine_learning_job_id": "linux_anomalous_network_service", - "name": "Unusual Linux Network Service", - "references": [ - "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" - ], - "risk_score": 21, - "rule_id": "52afbdc5-db15-596e-bc35-f5707f820c4b", - "severity": "low", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "ML" - ], - "type": "machine_learning", - "version": 4 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_url_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_url_activity.json deleted file mode 100644 index dce994c7192c5..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_url_activity.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "anomaly_threshold": 50, - "author": [ - "Elastic" - ], - "description": "A machine learning job detected an unusual web URL request from a Linux host, which can indicate malware delivery and execution. Wget and cURL are commonly used by Linux programs to download code and data. Most of the time, their usage is entirely normal. Generally, because they use a list of URLs, they repeatedly download from the same locations. However, Wget and cURL are sometimes used to deliver Linux exploit payloads, and threat actors use these tools to download additional software and code. For these reasons, unusual URLs can indicate unauthorized downloads or threat activity.", - "false_positives": [ - "A new and unusual program or artifact download in the course of software upgrades, debugging, or troubleshooting could trigger this alert." - ], - "from": "now-45m", - "interval": "15m", - "license": "Elastic License v2", - "machine_learning_job_id": "linux_anomalous_network_url_activity_ecs", - "name": "Unusual Linux Web Activity", - "references": [ - "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" - ], - "risk_score": 21, - "rule_id": "52afbdc5-db15-485e-bc35-f5707f820c4c", - "severity": "low", - "tags": [ - "Elastic", - "Host", - "Linux", - "Threat Detection", - "ML" - ], - "type": "machine_learning", - "version": 4 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json index eed66a25e89a1..76c8cd29fc8e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_process_all_hosts.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "linux_anomalous_process_all_hosts_ecs", - "v2_linux_anomalous_process_all_hosts_ecs" + "v3_linux_anomalous_process_all_hosts" ], "name": "Anomalous Process For a Linux Population", "note": "## Triage and analysis\n\n### Investigating an Unusual Linux Process\nDetection alerts from this rule indicate the presence of a Linux process that is rare and unusual for all of the monitored Linux hosts for which Auditbeat data is available. 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 arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_sudo_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_sudo_activity.json index 38a61fd205b2f..b08aa9dbd1a8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_sudo_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_sudo_activity.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_rare_sudo_user", + "machine_learning_job_id": [ + "v3_linux_rare_sudo_user" + ], "name": "Unusual Sudo Activity", "risk_score": 21, "rule_id": "1e9fc667-9ff1-4b33-9f40-fefca8537eb0", @@ -55,5 +57,5 @@ } ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json index 84fc762929b1c..c8b3f97d77f1f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_user_name.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "linux_anomalous_user_name_ecs", - "v2_linux_anomalous_user_name_ecs" + "v3_linux_anomalous_user_name" ], "name": "Unusual Linux Username", "note": "## Triage and analysis\n\n### Investigating an Unusual Linux User\nDetection alerts from this rule indicate activity for a Linux user name that is rare and unusual. 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? Could this be related to troubleshooting or debugging activity by a developer or site reliability engineer?\n- Examine the history of user activity. If this user only manifested recently, it might be a service account for 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 arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks that the user is performing.", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_information_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_information_discovery.json index ea1566b303861..f859d94cd7415 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_information_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_information_discovery.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_system_information_discovery", + "machine_learning_job_id": [ + "v3_linux_system_information_discovery" + ], "name": "Unusual Linux System Information Discovery Activity", "risk_score": 21, "rule_id": "d4af3a06-1e0a-48ec-b96a-faf2309fae46", @@ -40,5 +42,5 @@ } ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_configuration_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_configuration_discovery.json index 4c723db96cbde..13be5a8d8d3e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_configuration_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_configuration_discovery.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_network_configuration_discovery", + "machine_learning_job_id": [ + "v3_linux_network_configuration_discovery" + ], "name": "Unusual Linux System Network Configuration Discovery", "risk_score": 21, "rule_id": "f9590f47-6bd5-4a49-bd49-a2f886476fb9", @@ -40,5 +42,5 @@ } ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_connection_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_connection_discovery.json index d6ff9a654b946..7e4448a1a27f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_connection_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_network_connection_discovery.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_network_connection_discovery", + "machine_learning_job_id": [ + "v3_linux_network_connection_discovery" + ], "name": "Unusual Linux Network Connection Discovery", "risk_score": 21, "rule_id": "c28c4d8c-f014-40ef-88b6-79a1d67cd499", @@ -40,5 +42,5 @@ } ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_process_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_process_discovery.json index 7980ac6195c25..38213f96573c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_process_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_process_discovery.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_system_process_discovery", + "machine_learning_job_id": [ + "v3_linux_system_process_discovery" + ], "name": "Unusual Linux Process Discovery Activity", "risk_score": 21, "rule_id": "5c983105-4681-46c3-9890-0c66d05e776b", @@ -40,5 +42,5 @@ } ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_user_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_user_discovery.json index 79233b5c31000..9485a438d8259 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_user_discovery.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_system_user_discovery.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "linux_system_user_discovery", + "machine_learning_job_id": [ + "v3_linux_system_user_discovery" + ], "name": "Unusual Linux System Owner or User Discovery Activity", "risk_score": 21, "rule_id": "59756272-1998-4b8c-be14-e287035c4d10", @@ -40,5 +42,5 @@ } ], "type": "machine_learning", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json index d89f2fcf9c045..c873e6d7c1d8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_process_by_host_linux.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "rare_process_by_host_linux_ecs", - "v2_rare_process_by_host_linux_ecs" + "v3_rare_process_by_host_linux" ], "name": "Unusual Process For a Linux Host", "note": "## Triage and analysis\n\n### Investigating an Unusual Linux Process\nDetection alerts from this rule indicate the presence of a Linux 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 arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks it is performing.", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } 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 7b1d4629d6b14..bf08c731bedd1 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 @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "rare_process_by_host_windows_ecs", - "v2_rare_process_by_host_windows_ecs" + "v3_rare_process_by_host_windows" ], "name": "Unusual Process For a Windows Host", "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.\nUnderstanding 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", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 10 + "version": 11 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_suspicious_login_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_suspicious_login_activity.json index 8652c3c3446f7..1c3902fc94c0b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_suspicious_login_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_suspicious_login_activity.json @@ -10,7 +10,7 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "suspicious_login_activity_ecs", + "machine_learning_job_id": "suspicious_login_activity", "name": "Unusual Login Activity", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -20,11 +20,10 @@ "severity": "low", "tags": [ "Elastic", - "Host", - "Linux", + "Authentication", "Threat Detection", "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json index deaf07a77c7be..d0eb0d70841df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_process.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_rare_metadata_process", - "v2_windows_rare_metadata_process" + "v3_windows_rare_metadata_process" ], "name": "Unusual Windows Process Calling the Metadata Service", "risk_score": 21, @@ -26,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json index 31e078b795256..343719523210f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_metadata_user.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_rare_metadata_user", - "v2_windows_rare_metadata_user" + "v3_windows_rare_metadata_user" ], "name": "Unusual Windows User Calling the Metadata Service", "risk_score": 21, @@ -26,5 +25,5 @@ "ML" ], "type": "machine_learning", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json index 4f17e9a029ad9..8c14bbafff088 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_anomalous_network_activity_ecs", - "v2_windows_anomalous_network_activity_ecs" + "v3_windows_anomalous_network_activity" ], "name": "Unusual Windows Network Activity", "note": "## Triage and analysis\n\n### Investigating Unusual Network Activity\nDetection alerts from this rule indicate the presence of network activity from a Windows process for which network activity is very unusual. Here are some possible avenues of investigation:\n- Consider the IP addresses, protocol and ports. Are these used by normal but infrequent network workflows? Are they expected or unexpected?\n- If the destination IP address is remote or external, does it associate with an expected domain, organization or geography? Note: avoid interacting directly with suspected malicious IP addresses.\n- Consider the user as identified by the username field. Is this network activity part of an expected workflow for the user who ran the program?\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 arguments, title 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.", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json index 782a80c53f9b3..b7c80f05828e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_path_activity.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_anomalous_path_activity_ecs", - "v2_windows_anomalous_path_activity_ecs" + "v3_windows_anomalous_path_activity" ], "name": "Unusual Windows Path Activity", "references": [ @@ -29,5 +28,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json index b1ef88c628f93..fe7a5f2311f52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_all_hosts.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_anomalous_process_all_hosts_ecs", - "v2_windows_anomalous_process_all_hosts_ecs" + "v3_windows_anomalous_process_all_hosts" ], "name": "Anomalous Process For a Windows Population", "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 all of the Windows hosts for which Winlogbeat data is available. 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. ", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json index cfe9e7ff1eaa3..77dee22bcf35a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_process_creation.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_anomalous_process_creation", - "v2_windows_anomalous_process_creation" + "v3_windows_anomalous_process_creation" ], "name": "Anomalous Windows Process Creation", "references": [ @@ -29,5 +28,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_script.json index b430dc01342fc..27fa1a548e963 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_script.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_script.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_script", + "machine_learning_job_id": [ + "v3_windows_anomalous_script" + ], "name": "Suspicious Powershell Script", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -26,5 +28,5 @@ "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_service.json index d0f03d86dbcca..bdc22e6bd8745 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_service.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_service.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_anomalous_service", + "machine_learning_job_id": [ + "v3_windows_anomalous_service" + ], "name": "Unusual Windows Service", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -26,5 +28,5 @@ "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json index e3887043338fc..bffcf3ee7eb15 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_user_name.json @@ -11,8 +11,7 @@ "interval": "15m", "license": "Elastic License v2", "machine_learning_job_id": [ - "windows_anomalous_user_name_ecs", - "v2_windows_anomalous_user_name_ecs" + "v3_windows_anomalous_user_name" ], "name": "Unusual Windows Username", "note": "## Triage and analysis\n\n### Investigating an Unusual Windows User\nDetection alerts from this rule indicate activity for a Windows user name that is rare and unusual. 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? Could this be related to occasional troubleshooting or support activity?\n- Examine the history of user activity. If this user only manifested recently, it might be a service account for 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 arguments, title and working directory. These may provide indications as to the source of the program or the nature of the tasks that the user 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.", @@ -30,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 7 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_runas_event.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_runas_event.json index efdcaa306e8e5..6c9845699b723 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_runas_event.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_runas_event.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_rare_user_runas_event", + "machine_learning_job_id": [ + "v3_windows_rare_user_runas_event" + ], "name": "Unusual Windows User Privilege Elevation Activity", "references": [ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" @@ -26,5 +28,5 @@ "ML" ], "type": "machine_learning", - "version": 4 + "version": 5 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_type10_remote_login.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_type10_remote_login.json index 643a85d3af816..44d20761419fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_type10_remote_login.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_rare_user_type10_remote_login.json @@ -10,7 +10,9 @@ "from": "now-45m", "interval": "15m", "license": "Elastic License v2", - "machine_learning_job_id": "windows_rare_user_type10_remote_login", + "machine_learning_job_id": [ + "v3_windows_rare_user_type10_remote_login" + ], "name": "Unusual Windows Remote User", "note": "## Triage and analysis\n\n### Investigating an Unusual Windows User\nDetection alerts from this rule indicate activity for a rare and unusual Windows RDP (remote desktop) user. Here are some possible avenues of investigation:\n- Consider the user as identified by the username field. Is the user part of a group who normally logs into Windows hosts using RDP (remote desktop protocol)? Is this logon activity part of an expected workflow for the user?\n- Consider the source of the login. If the source is remote, could this be related to occasional troubleshooting or support activity by a vendor or an employee working remotely?", "references": [ @@ -27,5 +29,5 @@ "ML" ], "type": "machine_learning", - "version": 5 + "version": 6 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json index f64aab9da8fff..2048d4597b117 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Adobe Hijack Persistence", - "note": "## Triage and analysis\n\n### Investigating Adobe Hijack Persistence\n\nAttackers can replace the `RdrCEF.exe` executable with their own to maintain their access, which will be launched\nwhenever Adobe Acrobat Reader is executed.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the IoCs identified.\n- Remove and block malicious artifacts identified on the triage.\n- Disable the involved accounts, or restrict their ability to log on remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Adobe Hijack Persistence\n\nAttackers can replace the `RdrCEF.exe` executable with their own to maintain their access, which will be launched\nwhenever Adobe Acrobat Reader is executed.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "file where event.type == \"creation\" and\n file.path : (\"?:\\\\Program Files (x86)\\\\Adobe\\\\Acrobat Reader DC\\\\Reader\\\\AcroCEF\\\\RdrCEF.exe\",\n \"?:\\\\Program Files\\\\Adobe\\\\Acrobat Reader DC\\\\Reader\\\\AcroCEF\\\\RdrCEF.exe\") and\n not process.name : \"msiexec.exe\"\n", "references": [ "https://twitter.com/pabraeken/status/997997818362155008" @@ -53,5 +53,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 11 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_crontab_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_crontab_creation.json new file mode 100644 index 0000000000000..3527b14b2de65 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_crontab_creation.json @@ -0,0 +1,55 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to create or modify a crontab via a process that is not crontab (i.e python, osascript, etc.). This activity should not be highly prevalent and could indicate the use of cron as a persistence mechanism by a threat actor.", + "from": "now-9m", + "index": [ + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Suspicious CronTab Creation or Modification", + "query": "file where event.type != \"deletion\" and process.name != null and \n file.path : \"/private/var/at/tabs/*\" and not process.executable == \"/usr/bin/crontab\"\n", + "references": [ + "https://taomm.org/PDFs/vol1/CH%200x02%20Persistence.pdf", + "https://theevilbit.github.io/beyond/beyond_0004/" + ], + "risk_score": 47, + "rule_id": "530178da-92ea-43ce-94c2-8877a826783d", + "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": "T1053", + "name": "Scheduled Task/Job", + "reference": "https://attack.mitre.org/techniques/T1053/", + "subtechnique": [ + { + "id": "T1053.003", + "name": "Cron", + "reference": "https://attack.mitre.org/techniques/T1053/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json index 16e62c4a7e6e0..da4f102eae2c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json @@ -13,7 +13,8 @@ ], "language": "kuery", "license": "Elastic License v2", - "name": "Account configured with never Expiring Password", + "name": "Account Configured with Never-Expiring Password", + "note": "## Triage and analysis\n\n### Investigating Account Configured with Never-Expiring Password\n\nActive Directory provides a setting that prevents users' passwords from expiring. Enabling this setting is bad practice and can expose\nenvironments to vulnerabilities that weaken security posture, especially when these accounts are privileged.\n\nThe setting is usually configured so a user account can act as a service account. Attackers can abuse these accounts to\npersist in the domain and maintain long-term access using compromised accounts with a never-expiring password set.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/source host during the past 48 hours.\n- Inspect the account for suspicious or abnormal behaviors in the alert timeframe.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n- Using user accounts as service accounts is a bad security practice and should not be allowed in the domain. The\nsecurity team should map and monitor potential benign true positives (B-TPs), especially if the account is privileged.\nFor cases in which user accounts cannot be avoided, Microsoft provides the Group Managed Service Accounts (gMSA) feature,\nwhich ensures that the account password is robust and changed regularly and automatically.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Reset the password of the account and update its password settings.\n- Search for other occurrences on the domain.\n - Using the [Active Directory PowerShell module](https://docs.microsoft.com/en-us/powershell/module/activedirectory/get-aduser):\n - `get-aduser -filter { passwordNeverExpires -eq $true -and enabled -eq $true } | ft`\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts, if\nany, are identified. Reset passwords for these accounts and other potentially compromised credentials, such as email,\nbusiness systems, and web services.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.action:\"modified-user-account\" and event.code:\"4738\" and message:\"'Don't Expire Password' - Enabled\" and not user.id:\"S-1-5-18\"\n", "references": [ "https://www.cert.ssi.gouv.fr/uploads/guide-ad.html#dont_expire", @@ -49,5 +50,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_emond_rules_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json index 75e27f6c33678..a2ab1c137fb15 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json @@ -12,9 +12,10 @@ "license": "Elastic License v2", "name": "Emond Rules Creation or Modification", "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", - "query": "file where event.type != \"deletion\" and\n file.path : (\"/private/etc/emond.d/rules/*.plist\", \"/etc/emon.d/rules/*.plist\")\n", + "query": "file where event.type != \"deletion\" and\n file.path : (\"/private/etc/emond.d/rules/*.plist\", \"/etc/emon.d/rules/*.plist\", \"/private/var/db/emondClients/*\")\n", "references": [ - "https://www.xorrior.com/emond-persistence/" + "https://www.xorrior.com/emond-persistence/", + "https://www.sentinelone.com/blog/how-malware-persists-on-macos/" ], "risk_score": 47, "rule_id": "a6bf4dd4-743e-4da8-8c03-3ebd753a6c90", @@ -52,5 +53,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/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 5197c5b9a67bb..68776fcdff429 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Creation of a Hidden Local User Account", - "note": "## Triage and analysis\n\n### Investigating Creation of a Hidden Local User Account\n\nAttackers can create accounts ending with a `$` symbol to make the account hidden to user enumeration utilities and\nbypass detections that identify computer accounts by this pattern to apply filters.\n\nThis rule uses registry events to identify the creation of local hidden accounts.\n\n#### Possible investigation steps\n\n- Identify the user account which performed the action and whether it should perform this kind of action.\n- Investigate the script execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positive (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Quarantine the involved host to prevent further post-compromise behavior.\n- Delete the hidden account.\n- Review the privileges of the involved accounts.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Creation of a Hidden Local User Account\n\nAttackers can create accounts ending with a `$` symbol to make the account hidden to user enumeration utilities and\nbypass detections that identify computer accounts by this pattern to apply filters.\n\nThis rule uses registry events to identify the creation of local hidden accounts.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positive (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Delete the hidden account.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "registry where registry.path : \"HKLM\\\\SAM\\\\SAM\\\\Domains\\\\Account\\\\Users\\\\Names\\\\*$\\\\\"\n", "references": [ "https://blog.menasec.net/2019/02/threat-hunting-6-hiding-in-plain-sights_8.html", @@ -54,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/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 493cc46dcc860..45f74b00c7253 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 @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Suspicious Startup Shell Folder Modification", - "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Folder Modification\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. These programs may also run with higher privileges which can be ideal for\nan attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\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 the same organization have similar entries.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\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 other network administrator activity. Before entering further investigation,\nit should be verified that this activity is not benign.\n\n### Related rules\n\n- Startup or Run Key Registry Modification - 97fc44d3-8dae-4019-ae83-298c3015600f\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\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. After understanding the source\nof the attack, you can use this information to search for similar indicators on other machines in the same environment.\n", + "note": "## Triage and analysis\n\n### Investigating Suspicious Startup Shell Folder Modification\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. These programs may also run with higher privileges which can be ideal for\nan attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\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- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\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 other network administrator activity. Before entering further investigation,\nit should be verified that this activity is not benign.\n\n### Related rules\n\n- Startup or Run Key Registry Modification - 97fc44d3-8dae-4019-ae83-298c3015600f\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\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", @@ -48,5 +48,5 @@ ], "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_google_workspace_admin_role_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json index 4678055d6a7e6..6f19c1f60c05a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json index 1fc8f4de93b72..6d08829d787f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json index eb018b490eb23..7c92fa8fd6721 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json index 881e1e8893afe..8c3018514febb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 10 + "version": 12 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json index a47c158e4af29..e9f510c112fbf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Modification of Accessibility Binaries", - "note": "## Triage and analysis\n\n### Investigating Potential Modification of Accessibility Binaries\n\nAdversaries may establish persistence and/or elevate privileges by executing malicious content triggered by\naccessibility features. Windows contains accessibility features that may be launched with a key combination before a\nuser has logged in (ex: when the user is on the Windows logon screen). An adversary can modify the way these programs\nare launched to get a command prompt or backdoor without logging in to the system.\n\nMore details can be found [here](https://attack.mitre.org/techniques/T1546/008/).\n\nThis rule looks for the execution of supposed accessibility binaries that don't match any of the accessibility features\nbinaries' original file names, which is likely a custom binary deployed by the attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the IoCs identified.\n- Remove and block malicious artifacts identified on the triage.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Potential Modification of Accessibility Binaries\n\nAdversaries may establish persistence and/or elevate privileges by executing malicious content triggered by\naccessibility features. Windows contains accessibility features that may be launched with a key combination before a\nuser has logged in (ex: when the user is on the Windows logon screen). An adversary can modify the way these programs\nare launched to get a command prompt or backdoor without logging in to the system.\n\nMore details can be found [here](https://attack.mitre.org/techniques/T1546/008/).\n\nThis rule looks for the execution of supposed accessibility binaries that don't match any of the accessibility features\nbinaries' original file names, which is likely a custom binary deployed by the attacker.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\", \"info\") and\n process.parent.name : (\"Utilman.exe\", \"winlogon.exe\") and user.name == \"SYSTEM\" and\n process.args :\n (\n \"C:\\\\Windows\\\\System32\\\\osk.exe\",\n \"C:\\\\Windows\\\\System32\\\\Magnify.exe\",\n \"C:\\\\Windows\\\\System32\\\\Narrator.exe\",\n \"C:\\\\Windows\\\\System32\\\\Sethc.exe\",\n \"utilman.exe\",\n \"ATBroker.exe\",\n \"DisplaySwitch.exe\",\n \"sethc.exe\"\n )\n and not process.pe.original_file_name in\n (\n \"osk.exe\",\n \"sethc.exe\",\n \"utilman2.exe\",\n \"DisplaySwitch.exe\",\n \"ATBroker.exe\",\n \"ScreenMagnifier.exe\",\n \"SR.exe\",\n \"Narrator.exe\",\n \"magnify.exe\",\n \"MAGNIFY.EXE\"\n )\n\n/* uncomment once in winlogbeat to avoid bypass with rogue process with matching pe original file name */\n/* and process.code_signature.subject_name == \"Microsoft Windows\" and process.code_signature.status == \"trusted\" */\n", "references": [ "https://www.elastic.co/blog/practical-security-engineering-stateful-detection" @@ -75,5 +75,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/persistence_redshift_instance_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_redshift_instance_creation.json new file mode 100644 index 0000000000000..305a951c54367 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_redshift_instance_creation.json @@ -0,0 +1,48 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the creation of an Amazon Redshift cluster. Unexpected creation of this cluster by a non-administrative user may indicate a permission or role issue with current users. If unexpected, the resource may not properly be configured and could introduce security vulnerabilities.", + "false_positives": [ + "Valid clusters 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. Cluster creations by 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 Redshift Cluster Creation", + "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:redshift.amazonaws.com and event.action:CreateCluster and event.outcome:success\n", + "references": [ + "https://docs.aws.amazon.com/redshift/latest/APIReference/API_CreateCluster.html" + ], + "risk_score": 21, + "rule_id": "015cca13-8832-49ac-a01b-a396114809f6", + "severity": "low", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Asset Visibility" + ], + "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_registry_uncommon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json index 2ac177bda0b06..ed8aa95d06696 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 @@ -67,5 +67,5 @@ "timeline_title": "Comprehensive Registry Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 7 + "version": 9 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json index 3b7ab83f4f289..c9127d73cae7a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json @@ -19,7 +19,7 @@ "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:(CreateRoute or CreateRouteTable) and \nevent.outcome:success\n", "references": [ - "https://docs.datadoghq.com/security_platform/default_rules/cloudtrail-aws-route-table-modified/", + "https://docs.datadoghq.com/security_platform/default_rules/aws-ec2-route-table-modified/", "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateRoute.html", "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateRouteTable" ], @@ -47,5 +47,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } 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 index 51ac1d7e37760..c7acbed8da482 100644 --- 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 @@ -20,7 +20,7 @@ "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.datadoghq.com/security_platform/default_rules/aws-ec2-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", @@ -51,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json index 9cc1b79e02567..6e1705cda69dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json @@ -49,5 +49,5 @@ "timeline_title": "Comprehensive Registry Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 8 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json index a3bcd8f8a92e0..3768eeed0e57a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json @@ -11,8 +11,8 @@ "language": "eql", "license": "Elastic License v2", "name": "AdminSDHolder SDProp Exclusion Added", - "note": "## Triage and analysis\n\n### Investigating AdminSDHolder SDProp Exclusion Added\n\nThe SDProp process compares the permissions on protected objects with those defined on the AdminSDHolder object. If the\npermissions on any of the protected accounts and groups do not match, it resets the permissions on the protected\naccounts and groups to match those defined in the domain AdminSDHolder object.\n\nThe dSHeuristics is a Unicode string attribute, in which each character in the string represents a heuristic that is\nused to determine the behavior of Active Directory.\n\nAdministrators can use the dSHeuristics attribute to exclude privilege groups from the SDProp process by setting the\n16th bit (dwAdminSDExMask) of the string to a certain value, which represents the group(s):\n\n* For example, to exclude the Account Operators group, an administrator would modify the string, so the 16th character\nis set to 1 (i.e., 0000000001000001).\n\nThe usage of this exclusion can leave the accounts unprotected and facilitate the misconfiguration of privileges for the\nexcluded groups, enabling attackers to add accounts to these groups to maintain long-term persistence with high\nprivileges.\n\nThis rule matches changes of the dsHeuristics object where the 16th bit is set to a value other than zero.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the value assigned to the 16th bit of the string on the `winlog.event_data.AttributeValue` field:\n - Account Operators eq 1\n - Server Operators eq 2\n - Print Operators eq 4\n - Backup Operators eq 8\n The field value can range from 0 to f (15). If more than one group is specified, the values will be summed together;\n for example, Backup Operators and Print Operators will set the `c` value on the bit.\n\n### False positive analysis\n\n- While this modification can be done legitimately, it is not best practice. Any potential benign true positive (B-TP)\nshould be mapped and reviewed by the security team for alternatives as this weakens the security of the privileged group.\n\n### Response and remediation\n\n- The change can be reverted by setting the dwAdminSDExMask (16th bit) to 0 in dSHeuristics.\n\n## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nDS Access >\nAudit Directory Service Changes (Success)\n```\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", - "query": "any where event.action == \"Directory Service Changes\" and\n event.code == \"5136\" and\n length(winlog.event_data.AttributeValue) > 15 and\n winlog.event_data.AttributeValue regex~ \"[0-9]{15}([1-9a-f]).*\"\n", + "note": "## Triage and analysis\n\n### Investigating AdminSDHolder SDProp Exclusion Added\n\nThe SDProp process compares the permissions on protected objects with those defined on the AdminSDHolder object. If the\npermissions on any of the protected accounts and groups do not match, it resets the permissions on the protected\naccounts and groups to match those defined in the domain AdminSDHolder object.\n\nThe dSHeuristics is a Unicode string attribute, in which each character in the string represents a heuristic that is\nused to determine the behavior of Active Directory.\n\nAdministrators can use the dSHeuristics attribute to exclude privilege groups from the SDProp process by setting the\n16th bit (dwAdminSDExMask) of the string to a certain value, which represents the group(s):\n\n* For example, to exclude the Account Operators group, an administrator would modify the string, so the 16th character\nis set to 1 (i.e., 0000000001000001).\n\nThe usage of this exclusion can leave the accounts unprotected and facilitate the misconfiguration of privileges for the\nexcluded groups, enabling attackers to add accounts to these groups to maintain long-term persistence with high\nprivileges.\n\nThis rule matches changes of the dsHeuristics object where the 16th bit is set to a value other than zero.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check the value assigned to the 16th bit of the string on the `winlog.event_data.AttributeValue` field:\n - Account Operators eq 1\n - Server Operators eq 2\n - Print Operators eq 4\n - Backup Operators eq 8\n The field value can range from 0 to f (15). If more than one group is specified, the values will be summed together;\n for example, Backup Operators and Print Operators will set the `c` value on the bit.\n\n### False positive analysis\n\n- While this modification can be done legitimately, it is not a best practice. Any potential benign true positive (B-TP)\nshould be mapped and reviewed by the security team for alternatives as this weakens the security of the privileged group.\n\n### Response and remediation\n\n- The change can be reverted by setting the dwAdminSDExMask (16th bit) to 0 in dSHeuristics.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Directory Service Changes' logging policy must be configured for (Success).\nSteps to implement the logging policy with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies >\nWindows Settings >\nSecurity Settings >\nAdvanced Audit Policies Configuration >\nAudit Policies >\nDS Access >\nAudit Directory Service Changes (Success)\n```\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "query": "any where event.action == \"Directory Service Changes\" and\n event.code == \"5136\" and\n winlog.event_data.AttributeLDAPDisplayName : \"dSHeuristics\" and\n length(winlog.event_data.AttributeValue) > 15 and\n winlog.event_data.AttributeValue regex~ \"[0-9]{15}([1-9a-f]).*\"\n", "references": [ "https://www.cert.ssi.gouv.fr/uploads/guide-ad.html#dsheuristics_bad", "https://petri.com/active-directory-security-understanding-adminsdholder-object" @@ -41,5 +41,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_ssh_authorized_keys_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json index 23d16f121921b..fc49e19895101 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json @@ -11,7 +11,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "SSH Authorized Keys File Modification", - "query": "event.category:file and event.type:(change or creation) and \n file.name:(\"authorized_keys\" or \"authorized_keys2\") and \n not process.executable:\n (/Library/Developer/CommandLineTools/usr/bin/git or \n /usr/local/Cellar/maven/*/libexec/bin/mvn or \n /Library/Java/JavaVirtualMachines/jdk*.jdk/Contents/Home/bin/java or \n /usr/bin/vim or \n /usr/local/Cellar/coreutils/*/bin/gcat or \n /usr/bin/bsdtar or\n /usr/bin/nautilus or \n /usr/bin/scp or\n /usr/bin/touch or \n /var/lib/docker/*)\n", + "query": "event.category:file and event.type:(change or creation) and \n file.name:(\"authorized_keys\" or \"authorized_keys2\") and \n not process.executable:\n (/Library/Developer/CommandLineTools/usr/bin/git or \n /usr/local/Cellar/maven/*/libexec/bin/mvn or \n /Library/Java/JavaVirtualMachines/jdk*.jdk/Contents/Home/bin/java or \n /usr/bin/vim or \n /usr/local/Cellar/coreutils/*/bin/gcat or \n /usr/bin/bsdtar or\n /usr/bin/nautilus or \n /usr/bin/scp or\n /usr/bin/touch or \n /var/lib/docker/* or\n /usr/bin/google_guest_agent)\n", "risk_score": 47, "rule_id": "2215b8bd-1759-4ffa-8ab8-55c8e6b32e7f", "severity": "medium", @@ -49,5 +49,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_startup_folder_file_written_by_suspicious_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json index b33f7305a5b4b..4726af49b501f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Startup Persistence by a Suspicious Process", - "note": "## Triage and analysis\n\n### Investigating Startup Persistence by a Suspicious Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule monitors for commonly abused processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Determine if activity is unique by validating if other machines in the organization have similar entries.\n- Retrieve the file:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators may add programs to this mechanism via command-line shells. Before the further investigation, \nverify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the identified indicators of compromise (IoCs).\n- Remove malicious artifacts identified on the triage.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Startup Persistence by a Suspicious Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule monitors for commonly abused processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators may add programs to this mechanism via command-line shells. Before the further investigation, \nverify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "file where event.type != \"deletion\" and\n user.domain != \"NT AUTHORITY\" and\n file.path : (\"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\\\\*\", \n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\StartUp\\\\*\") and\n process.name : (\"cmd.exe\",\n \"powershell.exe\",\n \"wmic.exe\",\n \"mshta.exe\",\n \"pwsh.exe\",\n \"cscript.exe\",\n \"wscript.exe\",\n \"regsvr32.exe\",\n \"RegAsm.exe\",\n \"rundll32.exe\",\n \"EQNEDT32.EXE\",\n \"WINWORD.EXE\",\n \"EXCEL.EXE\",\n \"POWERPNT.EXE\",\n \"MSPUB.EXE\",\n \"MSACCESS.EXE\",\n \"iexplore.exe\",\n \"InstallUtil.exe\")\n", "risk_score": 47, "rule_id": "440e2db4-bc7f-4c96-a068-65b78da59bde", @@ -50,5 +50,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/persistence_startup_folder_file_written_by_unsigned_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json index 89022325ab5f6..a6b891b446d10 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json @@ -10,7 +10,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Startup Folder Persistence via Unsigned Process", - "note": "## Triage and analysis\n\n### Investigating Startup Folder Persistence via Unsigned Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for unsigned processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Determine if activity is unique by validating if other machines in the organization have similar entries.\n- Retrieve the file:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to Startup folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before entering further\ninvestigation, verify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the identified indicators of compromise (IoCs).\n- Remove malicious artifacts identified on the triage.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n", + "note": "## Triage and analysis\n\n### Investigating Startup Folder Persistence via Unsigned Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for unsigned processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a high possibility of benign legitimate programs being added to Startup folders. This activity could be based\non new software installations, patches, or any kind of network administrator related activity. Before entering further\ninvestigation, verify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "sequence by host.id, process.entity_id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\") and process.code_signature.trusted == false and\n /* suspicious paths can be added here */\n process.executable : (\"C:\\\\Users\\\\*.exe\", \n \"C:\\\\ProgramData\\\\*.exe\", \n \"C:\\\\Windows\\\\Temp\\\\*.exe\", \n \"C:\\\\Windows\\\\Tasks\\\\*.exe\", \n \"C:\\\\Intel\\\\*.exe\", \n \"C:\\\\PerfLogs\\\\*.exe\")\n ]\n [file where event.type != \"deletion\" and user.domain != \"NT AUTHORITY\" and\n file.path : (\"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\\\\*\", \n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\StartUp\\\\*\")\n ]\n", "risk_score": 41, "rule_id": "2fba96c0-ade5-4bce-b92f-a5df2509da3f", @@ -47,5 +47,5 @@ } ], "type": "eql", - "version": 3 + "version": 4 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json index bee5bf58ea39d..477219cf010bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Persistent Scripts in the Startup Directory", - "note": "## Triage and analysis\n\n### Investigating Persistent Scripts in the Startup Directory\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for shortcuts created by wscript.exe or cscript.exe, or js/vbs scripts created by any process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Determine if activity is unique by validating if other machines in the organization have similar entries.\n- Retrieve the script file:\n - Use a sandboxed malware analysis system to perform analysis.\n - Observe attempts to contact external domains and addresses.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- There is a low possibility of benign legitimate scripts being added to Startup folders. Validate whether this activity\nis benign.\n\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Startup Folder Persistence via Unsigned Process - 2fba96c0-ade5-4bce-b92f-a5df2509da3f\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the identified indicators of compromise (IoCs).\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Persistent Scripts in the Startup Directory\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule looks for shortcuts created by wscript.exe or cscript.exe, or js/vbs scripts created by any process.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Startup Folder Persistence via Unsigned Process - 2fba96c0-ade5-4bce-b92f-a5df2509da3f\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "file where event.type != \"deletion\" and user.domain != \"NT AUTHORITY\" and\n \n /* detect shortcuts created by wscript.exe or cscript.exe */\n (file.path : \"C:\\\\*\\\\Programs\\\\Startup\\\\*.lnk\" and\n process.name : (\"wscript.exe\", \"cscript.exe\")) or\n\n /* detect vbs or js files created by any process */\n file.path : (\"C:\\\\*\\\\Programs\\\\Startup\\\\*.vbs\", \n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.vbe\", \n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.wsh\", \n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.wsf\", \n \"C:\\\\*\\\\Programs\\\\Startup\\\\*.js\")\n", "risk_score": 47, "rule_id": "f7c4dc5a-a58d-491d-9f14-9b66507121c0", @@ -50,5 +50,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/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json index 150b886c70b87..bb31786d8fb91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json @@ -10,7 +10,8 @@ "language": "eql", "license": "Elastic License v2", "name": "Component Object Model Hijacking", - "query": "registry where\n /* uncomment once length is stable length(bytes_written_string) > 0 and */\n (registry.path : \"HK*}\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\") \n or\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*Classes\\\\*\\\\InprocServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\LocalServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\DelegateExecute\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\TreatAs\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\CLSID\\\\*\\\\ScriptletURL\\\\\") and\n not (process.executable : \"?:\\\\Program Files*\\\\Veeam\\\\Backup and Replication\\\\Console\\\\veeam.backup.shell.exe\" and\n registry.path : \"HKEY_USERS\\\\S-1-5-21-*_Classes\\\\CLSID\\\\*\\\\LocalServer32\\\\\") and\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\")\n", + "note": "## Triage and analysis\n\n### Investigating Component Object Model Hijacking\n\nAdversaries can insert malicious code that can be executed in place of legitimate software through hijacking the COM references and relationships as a means of persistence.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file referenced in the registry and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Some Microsoft executables will reference the LocalServer32 registry key value for the location of external COM objects.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "query": "registry where\n (registry.path : \"HK*}\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\") \n or\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*Classes\\\\*\\\\InprocServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\LocalServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\DelegateExecute\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\*\\\\TreatAs\\\\\", \n \"HKEY_USERS\\\\*Classes\\\\CLSID\\\\*\\\\ScriptletURL\\\\\") and\n not (process.executable : \"?:\\\\Program Files*\\\\Veeam\\\\Backup and Replication\\\\Console\\\\veeam.backup.shell.exe\" and\n registry.path : \"HKEY_USERS\\\\S-1-5-21-*_Classes\\\\CLSID\\\\*\\\\LocalServer32\\\\\") and\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\"\n ) and\n /* removes false-positives generated by OneDrive and Teams */\n not process.name : (\"OneDrive.exe\",\"OneDriveSetup.exe\",\"FileSyncConfig.exe\",\"Teams.exe\") and\n /* Teams DLL loaded by regsvr */\n not (process.name: \"regsvr32.exe\" and\n registry.data.strings : \"*Microsoft.Teams.*.dll\")\n", "references": [ "https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/" ], @@ -50,5 +51,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 6 + "version": 7 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json index 8a06669f42fc5..d5f1379a835e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "System Shells via Services", - "note": "## Triage and analysis\n\n### Investigating System Shells via Services\n\nAttackers may configure existing services or create new ones to execute system shells to elevate their privileges from\nadministrator to SYSTEM. They can also configure services to execute these shells with persistence payloads.\n\nThis rule looks for system shells being spawned by `services.exe`, which is compatible with the above behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify how the service was created or modified. Look for registry changes events or Windows events related to\nservice activities (for example, 4697 and/or 7045).\n - Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Check for commands executed under the spawned shell.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Delete the service or restore it to the original configuration.\n- Investigate the initial attack vector.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating System Shells via Services\n\nAttackers may configure existing services or create new ones to execute system shells to elevate their privileges from\nadministrator to SYSTEM. They can also configure services to execute these shells with persistence payloads.\n\nThis rule looks for system shells being spawned by `services.exe`, which is compatible with the above behavior.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify how the service was created or modified. Look for registry changes events or Windows events related to\nservice activities (for example, 4697 and/or 7045).\n - Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Check for commands executed under the spawned shell.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true positive\n(B-TP), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Delete the service or restore it to the original configuration.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.parent.name : \"services.exe\" and\n process.name : (\"cmd.exe\", \"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") and\n \n /* Third party FP's */\n not process.args : \"NVDisplay.ContainerLocalSystem\"\n", "risk_score": 47, "rule_id": "0022d47d-39c7-4f69-a232-4fe9dc7a3acd", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 11 + "version": 12 } 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 e85a08f63959d..3558fcd594e4a 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "User Added to Privileged Group in Active Directory", - "note": "## Triage and analysis\n\n### Investigating User Added to Privileged Group in Active Directory\n\nPrivileged accounts and groups in Active Directory are those to which powerful rights, privileges, and permissions are\ngranted that allow them to perform nearly any action in Active Directory and on domain-joined systems.\n\nAttackers can add users to privileged groups to maintain a level of access if their other privileged accounts are\nuncovered by the security team. This allows them to keep operating after the security team discovers abused accounts.\n\nThis rule monitors events related to a user being added to a privileged group.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should manage members of this group.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user during the past 48 hours.\n\n### False positive analysis\n\n- This attack abuses a legitimate Active Directory mechanism, so it is important to determine whether the activity is\nlegitimate, if the administrator is authorized to perform this operation, and if there is a need to grant the account\nthis level of privilege.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If the admin is not aware of the operation, activate your Active Directory incident response plan.\n- If the user does not need the administrator privileges, remove the account from the privileged group.\n\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating User Added to Privileged Group in Active Directory\n\nPrivileged accounts and groups in Active Directory are those to which powerful rights, privileges, and permissions are\ngranted that allow them to perform nearly any action in Active Directory and on domain-joined systems.\n\nAttackers can add users to privileged groups to maintain a level of access if their other privileged accounts are\nuncovered by the security team. This allows them to keep operating after the security team discovers abused accounts.\n\nThis rule monitors events related to a user being added to a privileged group.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should manage members of this group.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This attack abuses a legitimate Active Directory mechanism, so it is important to determine whether the activity is\nlegitimate, if the administrator is authorized to perform this operation, and if there is a need to grant the account\nthis level of privilege.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If the admin is not aware of the operation, activate your Active Directory incident response plan.\n- If the user does not need the administrator privileges, remove the account from the privileged group.\n- Review the privileges of the administrator account that performed the action.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "iam where event.action == \"added-member-to-group\" and\n group.name : (\"Admin*\",\n \"Local Administrators\",\n \"Domain Admins\",\n \"Enterprise Admins\",\n \"Backup Admins\",\n \"Schema Admins\",\n \"DnsAdmins\",\n \"Exchange Organization Administrators\")\n", "references": [ "https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-b--privileged-accounts-and-groups-in-active-directory" @@ -46,5 +46,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/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json index d4f289a3e8988..7cfbc30fca454 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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "User Account Creation", - "note": "## Triage and analysis\n\n### Investigating User Account Creation\n\nAttackers may create new accounts (both local and domain) to maintain access to victim systems.\n\nThis rule identifies the usage of `net.exe` to create new accounts.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree).\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Identify if the account was added to privileged groups or assigned special privileges after creation.\n- Investigate other alerts related to the user/host in the last 48 hours.\n\n### False positive analysis\n\n- Account creation is a common administrative task, so there is a high chance of the activity being legitimate. Before\ninvestigating further, verify that this activity is not benign.\n\n### Related rules\n\n- Creation of a Hidden Local User Account - 2edc8076-291e-41e9-81e4-e3fcbc97ae5e\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Delete the created account.\n- Reset the password for the user account leveraged to create the new account.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating User Account Creation\n\nAttackers may create new accounts (both local and domain) to maintain access to victim systems.\n\nThis rule identifies the usage of `net.exe` to create new accounts.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Identify if the account was added to privileged groups or assigned special privileges after creation.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- Account creation is a common administrative task, so there is a high chance of the activity being legitimate. Before\ninvestigating further, verify that this activity is not benign.\n\n### Related rules\n\n- Creation of a Hidden Local User Account - 2edc8076-291e-41e9-81e4-e3fcbc97ae5e\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Delete the created account.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type in (\"start\", \"process_started\") and\n process.name : (\"net.exe\", \"net1.exe\") and\n not process.parent.name : \"net.exe\" and\n (process.args : \"user\" and process.args : (\"/ad\", \"/add\"))\n", "risk_score": 21, "rule_id": "1aa9181a-492b-4c01-8b16-fa0735786b2b", @@ -50,5 +50,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 10 + "version": 11 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json index a75c18acbe04e..8ed57e28caaaf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json @@ -13,7 +13,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Startup/Logon Script added to Group Policy Object", - "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects (GPOs) can be used by attackers to instruct arbitrarily large groups of\nclients to execute specified commands at startup, logon, shutdown, and logoff. This is done by creating or modifying the\n`scripts.ini` or `psscripts.ini` files. The scripts are stored in the following path: `\\Machine\\Scripts\\`,\n`\\User\\Scripts\\`\n\n#### Possible investigation steps\n\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the\nactivity is legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the script file, and check for any potentially malicious commands and binaries.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Scope which objects have been affected.\n\n### False positive analysis\n\n- Verify if the execution is legitimately authorized and executed under a change management process.\n\n### Related rules\n\n- Group Policy Abuse for Privilege Addition - b9554892-5e0e-424b-83a0-5aef95aa43bf\n- Scheduled Task Execution at Scale via GPO - 15a8ba77-1c13-4274-88fe-6bd14133861e\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The investigation and containment must be performed in every computer controlled by the GPO, where necessary.\n- Remove the script from the GPO.\n- Check if other GPOs have suspicious scripts attached.\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects (GPOs) can be used by attackers to instruct arbitrarily large groups of\nclients to execute specified commands at startup, logon, shutdown, and logoff. This is done by creating or modifying the\n`scripts.ini` or `psscripts.ini` files. The scripts are stored in the following path: `\\Machine\\Scripts\\`,\n`\\User\\Scripts\\`\n\n#### Possible investigation steps\n\n- This attack abuses a legitimate mechanism of Active Directory, so it is important to determine whether the activity\nis legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `ScheduledTasks.xml` file, and check the `` and `` XML tags for any\npotentially malicious commands or binaries.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Scope which objects may be compromised by retrieving information about which objects are controlled by the GPO.\n\n### False positive analysis\n\n- Verify if the execution is legitimately authorized and executed under a change management process.\n\n### Related rules\n\n- Group Policy Abuse for Privilege Addition - b9554892-5e0e-424b-83a0-5aef95aa43bf\n- Scheduled Task Execution at Scale via GPO - 15a8ba77-1c13-4274-88fe-6bd14133861e\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The investigation and containment must be performed in every computer controlled by the GPO, where necessary.\n- Remove the script from the GPO.\n- Check if other GPOs have suspicious scripts attached.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", "query": "(\n event.code:5136 and winlog.event_data.AttributeLDAPDisplayName:(gPCMachineExtensionNames or gPCUserExtensionNames) and\n winlog.event_data.AttributeValue:(*42B5FAAE-6536-11D2-AE5A-0000F87571E3* and\n (*40B66650-4972-11D1-A7CA-0000F87571E3* or *40B6664F-4972-11D1-A7CA-0000F87571E3*))\n)\nor\n(\n event.code:5145 and winlog.event_data.ShareName:\\\\\\\\*\\\\SYSVOL and\n winlog.event_data.RelativeTargetName:(*\\\\scripts.ini or *\\\\psscripts.ini) and\n (message:WriteData or winlog.event_data.AccessList:*%%4417*)\n)\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0025_windows_audit_directory_service_changes.md", @@ -62,5 +62,5 @@ ], "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/privilege_escalation_group_policy_privileged_groups.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json index 5d1fbdf53705f..2e77e602e2e9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json @@ -10,7 +10,7 @@ "language": "kuery", "license": "Elastic License v2", "name": "Group Policy Abuse for Privilege Addition", - "note": "## Triage and analysis\n\n### Investigating Group Policy Abuse for Privilege Addition\n\nGroup Policy Objects (GPOs) can be used to add rights and/or modify Group Membership on GPOs by changing the contents of an INF\nfile named GptTmpl.inf, which is responsible for storing every setting under the Security Settings container in the GPO.\nThis file is unique for each GPO, and only exists if the GPO contains security settings.\nExample Path: \"\\\\DC.com\\SysVol\\DC.com\\Policies\\{PolicyGUID}\\Machine\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf\"\n\n#### Possible investigation steps\n\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the\nactivity is legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `GptTmpl.inf` file, and under the `Privilege Rights` section, look for potentially\ndangerous high privileges, for example: SeTakeOwnershipPrivilege, SeEnableDelegationPrivilege, etc.\n- Inspect the user security identifiers (SIDs) associated with these privileges, and if they should have these privileges.\n\n### False positive analysis\n\n- Inspect whether the user that has done the modifications should be allowed to. The user name can be found in the\n`winlog.event_data.SubjectUserName` field.\n\n### Related rules\n\n- Scheduled Task Execution at Scale via GPO - 15a8ba77-1c13-4274-88fe-6bd14133861e\n- Startup/Logon Script added to Group Policy Object - 16fac1a1-21ee-4ca6-b720-458e3855d046\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The investigation and containment must be performed in every computer controlled by the GPO, where necessary.\n- Remove the script from the GPO.\n- Check if other GPOs have suspicious scripts attached.\n\n## Config\n\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Group Policy Abuse for Privilege Addition\n\nGroup Policy Objects (GPOs) can be used to add rights and/or modify Group Membership on GPOs by changing the contents of an INF\nfile named GptTmpl.inf, which is responsible for storing every setting under the Security Settings container in the GPO.\nThis file is unique for each GPO, and only exists if the GPO contains security settings.\nExample Path: \"\\\\DC.com\\SysVol\\DC.com\\Policies\\{PolicyGUID}\\Machine\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf\"\n\n#### Possible investigation steps\n\n- This attack abuses a legitimate mechanism of Active Directory, so it is important to determine whether the activity\nis legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `GptTmpl.inf` file, and under the `Privilege Rights` section, look for potentially\ndangerous high privileges, for example: SeTakeOwnershipPrivilege, SeEnableDelegationPrivilege, etc.\n- Inspect the user security identifiers (SIDs) associated with these privileges, and if they should have these privileges.\n\n### False positive analysis\n\n- Inspect whether the user that has done the modifications should be allowed to. The user name can be found in the\n`winlog.event_data.SubjectUserName` field.\n\n### Related rules\n\n- Scheduled Task Execution at Scale via GPO - 15a8ba77-1c13-4274-88fe-6bd14133861e\n- Startup/Logon Script added to Group Policy Object - 16fac1a1-21ee-4ca6-b720-458e3855d046\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The investigation and containment must be performed in every computer controlled by the GPO, where necessary.\n- Remove the script from the GPO.\n- Check if other GPOs have suspicious scripts attached.\n\n## Config\n\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", "query": "event.code: \"5136\" and winlog.event_data.AttributeLDAPDisplayName:\"gPCMachineExtensionNames\" and \nwinlog.event_data.AttributeValue:(*827D319E-6EAC-11D2-A4EA-00C04F79F83A* and *803E14A0-B4FB-11D0-A0D0-00A0C90F574B*)\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0025_windows_audit_directory_service_changes.md", @@ -53,5 +53,5 @@ ], "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/privilege_escalation_group_policy_scheduled_task.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json index d2322eb07066a..34e4c0610e1a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json @@ -10,14 +10,14 @@ "language": "kuery", "license": "Elastic License v2", "name": "Scheduled Task Execution at Scale via GPO", - "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects (GPOs) can be used by attackers to execute scheduled tasks at scale to compromise objects controlled\nby a given GPO. This is done by changing the contents of the `\\Machine\\Preferences\\ScheduledTasks\\ScheduledTasks.xml`\nfile.\n\n#### Possible investigation steps\n\n- This attack abuses a legitimate mechanism of the Active Directory, so it is important to determine whether the activity\nis legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `ScheduledTasks.xml` file, and check the `` and `` XML tags for any\npotentially malicious commands and binaries.\n- Investigate other alerts related to the user/host in the last 48 hours.\n- Scope which objects have been affected.\n\n### False positive analysis\n\n- Verify if the execution is allowed and done under change management, and if the execution is legitimate.\n\n### Related rules\n\n- Group Policy Abuse for Privilege Addition - b9554892-5e0e-424b-83a0-5aef95aa43bf\n- Startup/Logon Script added to Group Policy Object - 16fac1a1-21ee-4ca6-b720-458e3855d046\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The investigation and containment must be performed in every computer controlled by the GPO, where necessary.\n- Remove the script from the GPO.\n- Check if other GPOs have suspicious scheduled tasks attached.\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", + "note": "## Triage and analysis\n\n### Investigating Scheduled Task Execution at Scale via GPO\n\nGroup Policy Objects (GPOs) can be used by attackers to execute scheduled tasks at scale to compromise objects controlled\nby a given GPO. This is done by changing the contents of the `\\Machine\\Preferences\\ScheduledTasks\\ScheduledTasks.xml`\nfile.\n\n#### Possible investigation steps\n\n- This attack abuses a legitimate mechanism of Active Directory, so it is important to determine whether the activity\nis legitimate and the administrator is authorized to perform this operation.\n- Retrieve the contents of the `ScheduledTasks.xml` file, and check the `` and `` XML tags for any\npotentially malicious commands or binaries.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Scope which objects may be compromised by retrieving information about which objects are controlled by the GPO.\n\n### False positive analysis\n\n- Verify if the execution is allowed and done under change management, and if the execution is legitimate.\n\n### Related rules\n\n- Group Policy Abuse for Privilege Addition - b9554892-5e0e-424b-83a0-5aef95aa43bf\n- Startup/Logon Script added to Group Policy Object - 16fac1a1-21ee-4ca6-b720-458e3855d046\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- The investigation and containment must be performed in every computer controlled by the GPO, where necessary.\n- Remove the script from the GPO.\n- Check if other GPOs have suspicious scheduled tasks attached.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nObject Access > \nAudit Detailed File Share (Success,Failure)\n```\n\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nPolicies > \nWindows Settings > \nSecurity Settings > \nAdvanced Audit Policies Configuration > \nAudit Policies > \nDS Access > \nAudit Directory Service Changes (Success,Failure)\n```\n", "query": "(event.code: \"5136\" and winlog.event_data.AttributeLDAPDisplayName:(\"gPCMachineExtensionNames\" or \"gPCUserExtensionNames\") and \n winlog.event_data.AttributeValue:(*CAB54552-DEEA-4691-817E-ED4A4D1AFC72* and *AADCED64-746C-4633-A97C-D61349046527*)) \nor\n(event.code: \"5145\" and winlog.event_data.ShareName: \"\\\\\\\\*\\\\SYSVOL\" and winlog.event_data.RelativeTargetName: *ScheduledTasks.xml and\n (message: WriteData or winlog.event_data.AccessList: *%%4417*))\n", "references": [ "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0025_windows_audit_directory_service_changes.md", "https://github.com/atc-project/atc-data/blob/f2bbb51ecf68e2c9f488e3c70dcdd3df51d2a46b/docs/Logging_Policies/LP_0029_windows_audit_detailed_file_share.md", "https://labs.f-secure.com/tools/sharpgpoabuse", "https://twitter.com/menasec1/status/1106899890377052160", - "https://github.com/SigmaHQ/sigma/blob/master/rules/windows/builtin/win_gpo_scheduledtasks.yml" + "https://github.com/SigmaHQ/sigma/blob/master/rules/windows/builtin/security/win_gpo_scheduledtasks.yml" ], "risk_score": 47, "rule_id": "15a8ba77-1c13-4274-88fe-6bd14133861e", @@ -68,5 +68,5 @@ ], "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/privilege_escalation_installertakeover.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json index 26e0a95503af9..dfcba2aa7e546 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Privilege Escalation via InstallerFileTakeOver", - "note": "## Triage and analysis\n\n### Investigating Potential Privilege Escalation via InstallerFileTakeOver\n\nInstallerFileTakeOver is a weaponized escalation of privilege proof of concept (EoP PoC) to the CVE-2021-41379 vulnerability. Upon successful exploitation, an\nunprivileged user will escalate privileges to SYSTEM/NT AUTHORITY.\n\nThis rule detects the default execution of the PoC, which overwrites the `elevation_service.exe` DACL and copies itself\nto the location to escalate privileges. An attacker is able to still take over any file that is not in use (locked),\nwhich is outside the scope of this rule.\n\n#### Possible investigation steps:\n\n- Check the executable's digital signature.\n- Look for additional processes spawned by the process, command lines, and network communications.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check for similar behavior in other hosts on the environment.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of the file.\n - Search for the existence and reputation of this file in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Verify whether a digital signature exists in the executable, and if it is valid.\n\n### Related rules\n\n- Suspicious DLL Loaded for Persistence or Privilege Escalation - bfeaf89b-a2a7-48a3-817f-e41829dc61ee\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement any temporary network rules, procedures, and segmentation required to contain the malware.\n - Immediately block the identified indicators of compromise (IoCs).\n- Remove and block malicious artifacts identified on the triage.\n- Disable user account\u2019s ability to log in remotely.\n- Reset passwords for the user account and other potentially compromised accounts (email, services, CRMs, etc.).\n- Determine the initial infection vector.\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", + "note": "## Triage and analysis\n\n### Investigating Potential Privilege Escalation via InstallerFileTakeOver\n\nInstallerFileTakeOver is a weaponized escalation of privilege proof of concept (EoP PoC) to the CVE-2021-41379 vulnerability. Upon successful exploitation, an\nunprivileged user will escalate privileges to SYSTEM/NT AUTHORITY.\n\nThis rule detects the default execution of the PoC, which overwrites the `elevation_service.exe` DACL and copies itself\nto the location to escalate privileges. An attacker is able to still take over any file that is not in use (locked),\nwhich is outside the scope of this rule.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Look for additional processes spawned by the process, command lines, and network communications.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled tasks creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Verify whether a digital signature exists in the executable, and if it is valid.\n\n### Related rules\n\n- Suspicious DLL Loaded for Persistence or Privilege Escalation - bfeaf89b-a2a7-48a3-817f-e41829dc61ee\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "/* This rule is compatible with both Sysmon and Elastic Endpoint */\n\nprocess where event.type == \"start\" and \n (?process.Ext.token.integrity_level_name : \"System\" or\n ?winlog.event_data.IntegrityLevel : \"System\") and\n (\n (process.name : \"elevation_service.exe\" and \n not process.pe.original_file_name == \"elevation_service.exe\") or\n\n (process.parent.name : \"elevation_service.exe\" and \n process.name : (\"rundll32.exe\", \"cmd.exe\", \"powershell.exe\")) \n )\n", "references": [ "https://github.com/klinix5/InstallerFileTakeOver" @@ -46,5 +46,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/privilege_escalation_krbrelayup_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json new file mode 100644 index 0000000000000..7cd11fbf31640 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json @@ -0,0 +1,73 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a suspicious local successful logon event where the Logon Package is Kerberos, the remote address is set to localhost, followed by a sevice creation from the same LogonId. This may indicate an attempt to leverage a Kerberos relay attack variant that can be used to elevate privilege locally from a domain joined user to local System privileges.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-system.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Service Creation via Local Kerberos Authentication", + "query": "sequence by host.id with maxspan=5m\n [authentication where\n\n /* event 4624 need to be logged */\n event.action == \"logged-in\" and event.outcome == \"success\" and\n\n /* authenticate locally using relayed kerberos Ticket */\n winlog.event_data.AuthenticationPackageName :\"Kerberos\" and winlog.logon.type == \"Network\" and\n cidrmatch(source.ip, \"127.0.0.0/8\", \"::1\") and source.port > 0] by winlog.event_data.TargetLogonId\n\n [any where\n /* event 4697 need to be logged */\n event.action : \"service-installed\"] by winlog.event_data.SubjectLogonId\n", + "references": [ + "https://github.com/Dec0ne/KrbRelayUp", + "https://googleprojectzero.blogspot.com/2021/10/using-kerberos-for-authentication-relay.html", + "https://github.com/cube0x0/KrbRelay", + "https://gist.github.com/tyranid/c24cfd1bd141d14d4925043ee7e03c82" + ], + "risk_score": 73, + "rule_id": "e4e31051-ee01-4307-a6ee-b21b186958f4", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation", + "Credential Access" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1543", + "name": "Create or Modify System Process", + "reference": "https://attack.mitre.org/techniques/T1543/", + "subtechnique": [ + { + "id": "T1543.003", + "name": "Windows Service", + "reference": "https://attack.mitre.org/techniques/T1543/003/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_suspicious_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_suspicious_logon.json new file mode 100644 index 0000000000000..05f7e52ade03c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_suspicious_logon.json @@ -0,0 +1,72 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a suspicious local successful logon event where the Logon Package is Kerberos, the remote address is set to localhost, and the target user SID is the built-in local Administrator account. This may indicate an attempt to leverage a Kerberos relay attack variant that can be used to elevate privilege locally from a domain joined limited user to local System privileges.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-system.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Potential Privilege Escalation via Local Kerberos Relay over LDAP", + "query": "authentication where\n\n /* event 4624 need to be logged */\n event.action == \"logged-in\" and event.outcome == \"success\" and\n\n /* authenticate locally via relayed kerberos ticket */\n winlog.event_data.AuthenticationPackageName : \"Kerberos\" and winlog.logon.type == \"Network\" and\n source.ip == \"127.0.0.1\" and source.port > 0 and\n\n /* Impersonate Administrator user via S4U2Self service ticket */\n winlog.event_data.TargetUserSid : \"S-1-5-21-*-500\"\n", + "references": [ + "https://github.com/Dec0ne/KrbRelayUp", + "https://googleprojectzero.blogspot.com/2021/10/using-kerberos-for-authentication-relay.html", + "https://github.com/cube0x0/KrbRelay" + ], + "risk_score": 73, + "rule_id": "3605a013-6f0c-4f7d-88a5-326f5be262ec", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation", + "Credential 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/", + "subtechnique": [ + { + "id": "T1548.002", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1548/002/" + } + ] + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1558", + "name": "Steal or Forge Kerberos Tickets", + "reference": "https://attack.mitre.org/techniques/T1558/" + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json index 4f924061b2294..f53cbab5c3827 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json @@ -18,7 +18,7 @@ "https://itm4n.github.io/windows-dll-hijacking-clarified/", "http://remoteawesomethoughts.blogspot.com/2019/05/windows-10-task-schedulerservice.html", "https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html", - "https://shellz.club/edgegdi-dll-for-persistence-and-lateral-movement/", + "https://shellz.club/2020/10/16/edgegdi-dll-for-persistence-and-lateral-movement.html", "https://windows-internals.com/faxing-your-way-to-system/", "http://waleedassar.blogspot.com/2013/01/wow64logdll.html" ], @@ -81,5 +81,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/privilege_escalation_printspooler_suspicious_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json index 1b14e8db0f83a..d3847aa99e1ae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json @@ -18,8 +18,7 @@ "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "file where event.type : \"deletion\" and\n not process.name : (\"spoolsv.exe\", \"dllhost.exe\", \"explorer.exe\") and\n file.path : \"?:\\\\Windows\\\\System32\\\\spool\\\\drivers\\\\x64\\\\3\\\\*.dll\"\n", "references": [ - "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527", - "https://github.com/afwu/PrintNightmare" + "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527" ], "risk_score": 47, "rule_id": "c4818812-d44f-47be-aaef-4cfb2f9cc799", @@ -50,5 +49,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/privilege_escalation_suspicious_dnshostname_update.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json new file mode 100644 index 0000000000000..07d373f3a2151 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json @@ -0,0 +1,61 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the remote update to a computer account's DnsHostName attribute. If the new value set is a valid domain controller DNS hostname and the subject computer name is not a domain controller, then it's highly likely a preparation step to exploit CVE-2022-26923 in an attempt to elevate privileges from a standard domain user to domain admin privileges.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-system.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Remote Computer Account DnsHostName Update", + "query": "sequence by host.id with maxspan=5m\n\n [authentication where event.action == \"logged-in\" and\n winlog.logon.type == \"Network\" and event.outcome == \"success\" and\n not user.name == \"ANONYMOUS LOGON\" and not winlog.event_data.SubjectUserName : \"*$\" and\n not user.domain == \"NT AUTHORITY\" and source.ip != \"127.0.0.1\" and source.ip !=\"::1\"] by winlog.event_data.TargetLogonId\n\n [iam where event.action == \"changed-computer-account\" and\n\n /* if DnsHostName value equal a DC DNS hostname then it's highly suspicious */\n winlog.event_data.DnsHostName : \"??*\"] by winlog.event_data.SubjectLogonId\n", + "references": [ + "https://research.ifcr.dk/certifried-active-directory-domain-privilege-escalation-cve-2022-26923-9e098fe298f4", + "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2022-26923" + ], + "risk_score": 73, + "rule_id": "6bed021a-0afb-461c-acbe-ffdb9574d3f3", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Privilege Escalation", + "Active Directory" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1068", + "name": "Exploitation for Privilege Escalation", + "reference": "https://attack.mitre.org/techniques/T1068/" + }, + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.002", + "name": "Domain Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/002/" + } + ] + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json index b603e25ecf1e5..0218eda10e75f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json @@ -18,8 +18,7 @@ "note": "## Config\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.\n", "query": "process where event.type == \"start\" and\n process.parent.name : \"spoolsv.exe\" and\n (?process.Ext.token.integrity_level_name : \"System\" or\n ?winlog.event_data.IntegrityLevel : \"System\") and\n\n /* exclusions for FP control below */\n not process.name : (\"splwow64.exe\", \"PDFCreator.exe\", \"acrodist.exe\", \"spoolsv.exe\", \"msiexec.exe\", \"route.exe\", \"WerFault.exe\") and\n not process.command_line : \"*\\\\WINDOWS\\\\system32\\\\spool\\\\DRIVERS*\" and\n not (process.name : \"net.exe\" and process.command_line : (\"*stop*\", \"*start*\")) and\n not (process.name : (\"cmd.exe\", \"powershell.exe\") and process.command_line : (\"*.spl*\", \"*\\\\program files*\", \"*route add*\")) and\n not (process.name : \"netsh.exe\" and process.command_line : (\"*add portopening*\", \"*rule name*\")) and\n not (process.name : \"regsvr32.exe\" and process.command_line : \"*PrintConfig.dll*\")\n", "references": [ - "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527", - "https://github.com/afwu/PrintNightmare" + "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527" ], "risk_score": 47, "rule_id": "ee5300a7-7e31-4a72-a258-250abb8b3aa1", @@ -50,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 5 + "version": 6 } From 9f78abfbe781c579d4b877aab968202c9864e37f Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 8 Jun 2022 12:53:16 -0700 Subject: [PATCH 07/44] [ci-stats] track size of shareable runtime (#133770) * [ci-stats] track size of sharable runtime * report more than just the total size of the bundle * put all sharable runtime metrics in a group * fix entryName find fn * remove unused import * update outdated snapshot * use runtime const and fix misspellings * remove errant empty comment --- package.json | 2 + packages/BUILD.bazel | 2 + .../kbn-optimizer-webpack-helpers/BUILD.bazel | 115 ++++++++++++++++++ .../kbn-optimizer-webpack-helpers/README.md | 3 + .../jest.config.js | 13 ++ .../package.json | 10 ++ .../src/index.ts | 27 ++++ .../src}/webpack_helpers.ts | 2 +- .../tsconfig.json | 17 +++ packages/kbn-optimizer/BUILD.bazel | 1 + .../optimizer_built_paths.test.ts | 1 - .../worker/populate_bundle_cache_plugin.ts | 16 +-- .../kbn-optimizer/src/worker/run_compilers.ts | 2 +- .../shareable_runtime/webpack.config.js | 10 +- .../webpack/ci_stats_plugin.ts | 112 +++++++++++++++++ .../webpack/runtime_size_limit.ts | 8 ++ yarn.lock | 8 ++ 17 files changed, 337 insertions(+), 12 deletions(-) create mode 100644 packages/kbn-optimizer-webpack-helpers/BUILD.bazel create mode 100644 packages/kbn-optimizer-webpack-helpers/README.md create mode 100644 packages/kbn-optimizer-webpack-helpers/jest.config.js create mode 100644 packages/kbn-optimizer-webpack-helpers/package.json create mode 100644 packages/kbn-optimizer-webpack-helpers/src/index.ts rename packages/{kbn-optimizer/src/worker => kbn-optimizer-webpack-helpers/src}/webpack_helpers.ts (99%) create mode 100644 packages/kbn-optimizer-webpack-helpers/tsconfig.json create mode 100644 x-pack/plugins/canvas/shareable_runtime/webpack/ci_stats_plugin.ts create mode 100644 x-pack/plugins/canvas/shareable_runtime/webpack/runtime_size_limit.ts diff --git a/package.json b/package.json index 1468f49be4722..fbf76e833a675 100644 --- a/package.json +++ b/package.json @@ -526,6 +526,7 @@ "@kbn/import-resolver": "link:bazel-bin/packages/kbn-import-resolver", "@kbn/jest-serializers": "link:bazel-bin/packages/kbn-jest-serializers", "@kbn/optimizer": "link:bazel-bin/packages/kbn-optimizer", + "@kbn/optimizer-webpack-helpers": "link:bazel-bin/packages/kbn-optimizer-webpack-helpers", "@kbn/performance-testing-dataset-extractor": "link:bazel-bin/packages/kbn-performance-testing-dataset-extractor", "@kbn/plugin-generator": "link:bazel-bin/packages/kbn-plugin-generator", "@kbn/plugin-helpers": "link:bazel-bin/packages/kbn-plugin-helpers", @@ -699,6 +700,7 @@ "@types/kbn__mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl/npm_module_types", "@types/kbn__monaco": "link:bazel-bin/packages/kbn-monaco/npm_module_types", "@types/kbn__optimizer": "link:bazel-bin/packages/kbn-optimizer/npm_module_types", + "@types/kbn__optimizer-webpack-helpers": "link:bazel-bin/packages/kbn-optimizer-webpack-helpers/npm_module_types", "@types/kbn__performance-testing-dataset-extractor": "link:bazel-bin/packages/kbn-performance-testing-dataset-extractor/npm_module_types", "@types/kbn__plugin-discovery": "link:bazel-bin/packages/kbn-plugin-discovery/npm_module_types", "@types/kbn__plugin-generator": "link:bazel-bin/packages/kbn-plugin-generator/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index ab2510940ae9c..4a67c4bd1978b 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -82,6 +82,7 @@ filegroup( "//packages/kbn-logging:build", "//packages/kbn-mapbox-gl:build", "//packages/kbn-monaco:build", + "//packages/kbn-optimizer-webpack-helpers:build", "//packages/kbn-optimizer:build", "//packages/kbn-performance-testing-dataset-extractor:build", "//packages/kbn-plugin-discovery:build", @@ -207,6 +208,7 @@ filegroup( "//packages/kbn-logging:build_types", "//packages/kbn-mapbox-gl:build_types", "//packages/kbn-monaco:build_types", + "//packages/kbn-optimizer-webpack-helpers:build_types", "//packages/kbn-optimizer:build_types", "//packages/kbn-performance-testing-dataset-extractor:build_types", "//packages/kbn-plugin-discovery:build_types", diff --git a/packages/kbn-optimizer-webpack-helpers/BUILD.bazel b/packages/kbn-optimizer-webpack-helpers/BUILD.bazel new file mode 100644 index 0000000000000..a27bcf4d31586 --- /dev/null +++ b/packages/kbn-optimizer-webpack-helpers/BUILD.bazel @@ -0,0 +1,115 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-optimizer-webpack-helpers" +PKG_REQUIRE_NAME = "@kbn/optimizer-webpack-helpers" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/webpack", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-optimizer-webpack-helpers/README.md b/packages/kbn-optimizer-webpack-helpers/README.md new file mode 100644 index 0000000000000..7ae5f9eb003ec --- /dev/null +++ b/packages/kbn-optimizer-webpack-helpers/README.md @@ -0,0 +1,3 @@ +# @kbn/optimizer-webpack-helpers + +Empty package generated by @kbn/generate diff --git a/packages/kbn-optimizer-webpack-helpers/jest.config.js b/packages/kbn-optimizer-webpack-helpers/jest.config.js new file mode 100644 index 0000000000000..b6697a0efb95a --- /dev/null +++ b/packages/kbn-optimizer-webpack-helpers/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-optimizer-webpack-helpers'], +}; diff --git a/packages/kbn-optimizer-webpack-helpers/package.json b/packages/kbn-optimizer-webpack-helpers/package.json new file mode 100644 index 0000000000000..ca65edcf9d017 --- /dev/null +++ b/packages/kbn-optimizer-webpack-helpers/package.json @@ -0,0 +1,10 @@ +{ + "name": "@kbn/optimizer-webpack-helpers", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0", + "kibana": { + "devOnly": true + } +} diff --git a/packages/kbn-optimizer-webpack-helpers/src/index.ts b/packages/kbn-optimizer-webpack-helpers/src/index.ts new file mode 100644 index 0000000000000..4c8d8a987c154 --- /dev/null +++ b/packages/kbn-optimizer-webpack-helpers/src/index.ts @@ -0,0 +1,27 @@ +/* + * 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 type { + WebpackConcatenatedModule, + WebpackDelegatedModule, + WebpackExternalModule, + WebpackIgnoredModule, + WebpackNormalModule, + WebpackResolveData, +} from './webpack_helpers'; + +export { + isFailureStats, + failedStatsToErrorMessage, + getModulePath, + isConcatenatedModule, + isDelegatedModule, + isExternalModule, + isIgnoredModule, + isNormalModule, +} from './webpack_helpers'; diff --git a/packages/kbn-optimizer/src/worker/webpack_helpers.ts b/packages/kbn-optimizer-webpack-helpers/src/webpack_helpers.ts similarity index 99% rename from packages/kbn-optimizer/src/worker/webpack_helpers.ts rename to packages/kbn-optimizer-webpack-helpers/src/webpack_helpers.ts index 4bae5035c9a06..2ad7ab8815c14 100644 --- a/packages/kbn-optimizer/src/worker/webpack_helpers.ts +++ b/packages/kbn-optimizer-webpack-helpers/src/webpack_helpers.ts @@ -7,7 +7,7 @@ */ import webpack from 'webpack'; -// @ts-ignore +// @ts-expect-error module is not typed import Stats from 'webpack/lib/Stats'; export function isFailureStats(stats: webpack.Stats) { diff --git a/packages/kbn-optimizer-webpack-helpers/tsconfig.json b/packages/kbn-optimizer-webpack-helpers/tsconfig.json new file mode 100644 index 0000000000000..a8cfc2cceb08b --- /dev/null +++ b/packages/kbn-optimizer-webpack-helpers/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kbn-optimizer/BUILD.bazel b/packages/kbn-optimizer/BUILD.bazel index 2e0590af9ea4d..e011a3f9561c4 100644 --- a/packages/kbn-optimizer/BUILD.bazel +++ b/packages/kbn-optimizer/BUILD.bazel @@ -66,6 +66,7 @@ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-dev-utils:npm_module_types", + "//packages/kbn-optimizer-webpack-helpers:npm_module_types", "//packages/kbn-std:npm_module_types", "//packages/kbn-ui-shared-deps-npm:npm_module_types", "//packages/kbn-ui-shared-deps-src:npm_module_types", diff --git a/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts b/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts index 164a855e76896..d7bcdf91ba9c3 100644 --- a/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts @@ -66,7 +66,6 @@ it(`finds all the optimizer files relative to it's path`, async () => { /node_modules/@kbn/optimizer/target_node/worker/run_compilers.js, /node_modules/@kbn/optimizer/target_node/worker/run_worker.js, /node_modules/@kbn/optimizer/target_node/worker/theme_loader.js, - /node_modules/@kbn/optimizer/target_node/worker/webpack_helpers.js, /node_modules/@kbn/optimizer/target_node/worker/webpack.config.js, ] `); diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts index cfa165d11a1fe..ac010da7a8340 100644 --- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts +++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts @@ -11,6 +11,14 @@ import Path from 'path'; import { inspect } from 'util'; import webpack from 'webpack'; +import { + isExternalModule, + isNormalModule, + isIgnoredModule, + isConcatenatedModule, + isDelegatedModule, + getModulePath, +} from '@kbn/optimizer-webpack-helpers'; import { Bundle, @@ -21,14 +29,6 @@ import { ParsedDllManifest, } from '../common'; import { BundleRefModule } from './bundle_ref_module'; -import { - isExternalModule, - isNormalModule, - isIgnoredModule, - isConcatenatedModule, - isDelegatedModule, - getModulePath, -} from './webpack_helpers'; /** * sass-loader creates about a 40% overhead on the overall optimizer runtime, and diff --git a/packages/kbn-optimizer/src/worker/run_compilers.ts b/packages/kbn-optimizer/src/worker/run_compilers.ts index 6578e5fed5738..ca69e195000ed 100644 --- a/packages/kbn-optimizer/src/worker/run_compilers.ts +++ b/packages/kbn-optimizer/src/worker/run_compilers.ts @@ -11,10 +11,10 @@ import 'source-map-support/register'; import webpack, { Stats } from 'webpack'; import * as Rx from 'rxjs'; import { mergeMap, map, mapTo, takeUntil } from 'rxjs/operators'; +import { isFailureStats, failedStatsToErrorMessage } from '@kbn/optimizer-webpack-helpers'; import { CompilerMsgs, CompilerMsg, maybeMap, Bundle, WorkerConfig, BundleRefs } from '../common'; import { getWebpackConfig } from './webpack.config'; -import { isFailureStats, failedStatsToErrorMessage } from './webpack_helpers'; const PLUGIN_NAME = '@kbn/optimizer'; diff --git a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js index 1a22af80da28d..09a0999194490 100644 --- a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js +++ b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js @@ -5,10 +5,13 @@ * 2.0. */ +require('../../../../src/setup_node_env'); + const path = require('path'); const webpack = require('webpack'); const { stringifyRequest } = require('loader-utils'); // eslint-disable-line +const { CiStatsPlugin } = require('./webpack/ci_stats_plugin'); const { KIBANA_ROOT, SHAREABLE_RUNTIME_OUTPUT, @@ -32,7 +35,6 @@ module.exports = { [SHAREABLE_RUNTIME_NAME]: require.resolve('./index.ts'), }, mode: isProd ? 'production' : 'development', - plugins: isProd ? [new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 })] : [], output: { path: SHAREABLE_RUNTIME_OUTPUT, filename: '[name].js', @@ -191,4 +193,10 @@ module.exports = { fs: 'empty', child_process: 'empty', }, + plugins: [ + isProd ? new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }) : [], + new CiStatsPlugin({ + entryName: SHAREABLE_RUNTIME_NAME, + }), + ].flat(), }; diff --git a/x-pack/plugins/canvas/shareable_runtime/webpack/ci_stats_plugin.ts b/x-pack/plugins/canvas/shareable_runtime/webpack/ci_stats_plugin.ts new file mode 100644 index 0000000000000..20facc666f47c --- /dev/null +++ b/x-pack/plugins/canvas/shareable_runtime/webpack/ci_stats_plugin.ts @@ -0,0 +1,112 @@ +/* + * 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. + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +import Path from 'path'; + +import webpack from 'webpack'; +import { ToolingLog } from '@kbn/tooling-log'; +import { REPO_ROOT } from '@kbn/utils'; +import normalizePath from 'normalize-path'; +import { CiStatsReporter } from '@kbn/ci-stats-reporter'; +import { isNormalModule, isConcatenatedModule } from '@kbn/optimizer-webpack-helpers'; +import { RUNTIME_SIZE_LIMIT } from './runtime_size_limit'; + +const IGNORED_EXTNAME = ['.map', '.br', '.gz']; + +interface Asset { + name: string; + size: number; +} + +export class CiStatsPlugin { + constructor( + private readonly options: { + entryName: string; + } + ) {} + + public apply(compiler: webpack.Compiler) { + const log = new ToolingLog({ + level: 'error', + writeTo: process.stdout, + }); + const ciStats = CiStatsReporter.fromEnv(log); + if (!ciStats.isEnabled()) { + return; + } + + compiler.hooks.emit.tapAsync('CiStatsPlugin', async (compilation) => { + const { entryName } = this.options; + + const assets = Object.entries(compilation.assets) + .map( + ([name, source]: [string, any]): Asset => ({ + name, + size: source.size(), + }) + ) + .filter((asset) => { + const filename = Path.basename(asset.name); + if (filename.startsWith('.')) { + return false; + } + + const ext = Path.extname(filename); + if (IGNORED_EXTNAME.includes(ext)) { + return false; + } + + return true; + }); + + const entry = assets.find((a) => a.name === `${entryName}.js`); + if (!entry) { + throw new Error(`Unable to find bundle entry named [${entryName}]`); + } + + const moduleCount = compilation.modules.reduce((acc, module) => { + if (isNormalModule(module)) { + return acc + 1; + } + + if (isConcatenatedModule(module)) { + return acc + module.modules.length; + } + + return acc; + }, 0); + + if (moduleCount === 0) { + throw new Error(`unable to determine module count`); + } + + await ciStats.metrics([ + { + group: `canvas shareable runtime`, + id: 'total size', + value: entry.size, + limit: RUNTIME_SIZE_LIMIT, + limitConfigPath: normalizePath( + Path.relative(REPO_ROOT, require.resolve('./runtime_size_limit')) + ), + }, + { + group: `canvas shareable runtime`, + id: 'misc asset size', + value: assets.filter((a) => a !== entry).reduce((acc: number, a) => acc + a.size, 0), + }, + { + group: `canvas shareable runtime`, + id: 'module count', + value: moduleCount, + }, + ]); + }); + } +} diff --git a/x-pack/plugins/canvas/shareable_runtime/webpack/runtime_size_limit.ts b/x-pack/plugins/canvas/shareable_runtime/webpack/runtime_size_limit.ts new file mode 100644 index 0000000000000..51b16b6ccbd52 --- /dev/null +++ b/x-pack/plugins/canvas/shareable_runtime/webpack/runtime_size_limit.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const RUNTIME_SIZE_LIMIT = 8_200_000; diff --git a/yarn.lock b/yarn.lock index 7d19e766e2399..369d99179cc15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3199,6 +3199,10 @@ version "0.0.0" uid "" +"@kbn/optimizer-webpack-helpers@link:bazel-bin/packages/kbn-optimizer-webpack-helpers": + version "0.0.0" + uid "" + "@kbn/optimizer@link:bazel-bin/packages/kbn-optimizer": version "0.0.0" uid "" @@ -6518,6 +6522,10 @@ version "0.0.0" uid "" +"@types/kbn__optimizer-webpack-helpers@link:bazel-bin/packages/kbn-optimizer-webpack-helpers/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__optimizer@link:bazel-bin/packages/kbn-optimizer/npm_module_types": version "0.0.0" uid "" From dd95fa2a383163198834f3fe752e2f3eae590f2c Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Wed, 8 Jun 2022 22:13:30 +0200 Subject: [PATCH 08/44] [SecuritySolution] Turn prevalence count into a button (#133791) * feat: turn prevalence count into a button - Ties in with other parts of the UI where we show counts that are actionable and start a timeline investigation - Uses less space in the flyout * fix: remove unused variable * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../components/event_details/summary_view.tsx | 8 -- .../table/add_to_timeline_cell.tsx | 93 ------------------- ...> investigate_in_timeline_button.test.tsx} | 58 +----------- .../table/investigate_in_timeline_button.tsx | 88 ++++++++++++++++++ .../event_details/table/prevalence_cell.tsx | 20 +++- 5 files changed, 109 insertions(+), 158 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx rename x-pack/plugins/security_solution/public/common/components/event_details/table/{add_to_timeline_cell.test.tsx => investigate_in_timeline_button.test.tsx} (61%) create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 59215dcb5b1b6..7946143f1d156 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -23,7 +23,6 @@ import { VIEW_ALL_FIELDS } from './translations'; import { SummaryTable } from './table/summary_table'; import { SummaryValueCell } from './table/summary_value_cell'; import { PrevalenceCellRenderer } from './table/prevalence_cell'; -import { AddToTimelineCellRenderer } from './table/add_to_timeline_cell'; const baseColumns: Array> = [ { @@ -60,13 +59,6 @@ const allColumns: Array> = [ align: 'right', width: '130px', }, - { - field: 'description', - truncateText: true, - render: AddToTimelineCellRenderer, - name: '', - width: '30px', - }, ]; const rowProps = { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx deleted file mode 100644 index fe379f9693f6e..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx +++ /dev/null @@ -1,93 +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 React from 'react'; -import { EuiButtonIcon } from '@elastic/eui'; -import { isEmpty } from 'lodash'; -import { useDispatch } from 'react-redux'; - -import { AlertSummaryRow, hasHoverOrRowActions } from '../helpers'; -import { inputsActions } from '../../../store/inputs'; -import { updateProviders } from '../../../../timelines/store/timeline/actions'; -import { sourcererActions } from '../../../store/actions'; -import { SourcererScopeName } from '../../../store/sourcerer/model'; -import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; -import { useActionCellDataProvider } from './use_action_cell_data_provider'; -import { useCreateTimeline } from '../../../../timelines/components/timeline/properties/use_create_timeline'; -import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; - -const AddToTimelineCell = React.memo( - ({ data, eventId, fieldFromBrowserField, linkValue, timelineId, values }) => { - const dispatch = useDispatch(); - - const actionCellConfig = useActionCellDataProvider({ - contextId: timelineId, - eventId, - field: data.field, - fieldFormat: data.format, - fieldFromBrowserField, - fieldType: data.type, - isObjectArray: data.isObjectArray, - linkValue, - values, - }); - - const clearTimeline = useCreateTimeline({ - timelineId: TimelineId.active, - timelineType: TimelineType.default, - }); - - const configureAndOpenTimeline = React.useCallback(() => { - if (actionCellConfig?.dataProvider) { - // Reset the current timeline - clearTimeline(); - // Update the timeline's providers to match the current prevalence field query - dispatch( - updateProviders({ - id: TimelineId.active, - providers: actionCellConfig.dataProvider, - }) - ); - // Only show detection alerts - // (This is required so the timeline event count matches the prevalence count) - dispatch( - sourcererActions.setSelectedDataView({ - id: SourcererScopeName.timeline, - selectedDataViewId: 'security-solution-default', - selectedPatterns: ['.alerts-security.alerts-default'], - }) - ); - // Unlock the time range from the global time range - dispatch(inputsActions.removeGlobalLinkTo()); - } - }, [dispatch, clearTimeline, actionCellConfig]); - - const fieldHasActionsEnabled = hasHoverOrRowActions(data.field); - const showButton = - values != null && !isEmpty(actionCellConfig?.dataProvider) && fieldHasActionsEnabled; - - if (showButton) { - return ( - - ); - } else { - return null; - } - } -); - -AddToTimelineCell.displayName = 'AddToTimelineCell'; - -export const AddToTimelineCellRenderer = (props: AlertSummaryRow['description']) => ( - -); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.test.tsx similarity index 61% rename from x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.test.tsx index 3171c91c17982..32382c7dfa51d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.test.tsx @@ -9,13 +9,12 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { BrowserField } from '../../../containers/source'; -import { AddToTimelineCellRenderer } from './add_to_timeline_cell'; +import { InvestigateInTimelineButton } from './investigate_in_timeline_button'; import { TestProviders } from '../../../mock'; import { EventFieldsData } from '../types'; import { TimelineId } from '../../../../../common/types'; import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; -import { AGENT_STATUS_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; jest.mock('../../../lib/kibana'); @@ -46,43 +45,12 @@ const hostIpData: EventFieldsData = { values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], }; -const agentStatusFieldFromBrowserField: BrowserField = { - aggregatable: true, - category: 'agent', - description: 'Agent status.', - fields: {}, - format: '', - indexes: ['auditbeat-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], - name: AGENT_STATUS_FIELD_NAME, - readFromDocValues: false, - searchable: true, - type: 'string', - example: 'status', -}; - -const agentStatusData: EventFieldsData = { - field: AGENT_STATUS_FIELD_NAME, - format: '', - type: '', - aggregatable: false, - description: '', - example: '', - category: '', - fields: {}, - indexes: [], - name: AGENT_STATUS_FIELD_NAME, - searchable: false, - readFromDocValues: false, - isObjectArray: false, - values: ['status'], -}; - -describe('AddToTimelineCellRenderer', () => { +describe('InvestigateInTimelineButton', () => { describe('When all props are provided', () => { test('it should display the add to timeline button', () => { render( - { test('it should not render', () => { render( - { expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).not.toBeInTheDocument(); }); }); - - describe('When the field is the host status field', () => { - test('it should not render', () => { - render( - - - - ); - expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).not.toBeInTheDocument(); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx new file mode 100644 index 0000000000000..49967c130f3ae --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { useDispatch } from 'react-redux'; + +import { AlertSummaryRow } from '../helpers'; +import { inputsActions } from '../../../store/inputs'; +import { updateProviders } from '../../../../timelines/store/timeline/actions'; +import { sourcererActions } from '../../../store/actions'; +import { SourcererScopeName } from '../../../store/sourcerer/model'; +import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; +import { useActionCellDataProvider } from './use_action_cell_data_provider'; +import { useCreateTimeline } from '../../../../timelines/components/timeline/properties/use_create_timeline'; +import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; + +export const InvestigateInTimelineButton = React.memo< + React.PropsWithChildren +>(({ data, eventId, fieldFromBrowserField, linkValue, timelineId, values, children }) => { + const dispatch = useDispatch(); + + const actionCellConfig = useActionCellDataProvider({ + contextId: timelineId, + eventId, + field: data.field, + fieldFormat: data.format, + fieldFromBrowserField, + fieldType: data.type, + isObjectArray: data.isObjectArray, + linkValue, + values, + }); + + const clearTimeline = useCreateTimeline({ + timelineId: TimelineId.active, + timelineType: TimelineType.default, + }); + + const configureAndOpenTimeline = React.useCallback(() => { + if (actionCellConfig?.dataProvider) { + // Reset the current timeline + clearTimeline(); + // Update the timeline's providers to match the current prevalence field query + dispatch( + updateProviders({ + id: TimelineId.active, + providers: actionCellConfig.dataProvider, + }) + ); + // Only show detection alerts + // (This is required so the timeline event count matches the prevalence count) + dispatch( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.timeline, + selectedDataViewId: 'security-solution-default', + selectedPatterns: ['.alerts-security.alerts-default'], + }) + ); + // Unlock the time range from the global time range + dispatch(inputsActions.removeGlobalLinkTo()); + } + }, [dispatch, clearTimeline, actionCellConfig]); + + const showButton = values != null && !isEmpty(actionCellConfig?.dataProvider); + + if (showButton) { + return ( + + {children} + + ); + } else { + return null; + } +}); + +InvestigateInTimelineButton.displayName = 'InvestigateInTimelineButton'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx index 71925a72dca83..fee1f4bbe2dd7 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx @@ -9,11 +9,12 @@ import React from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { AlertSummaryRow } from '../helpers'; -import { defaultToEmptyTag } from '../../empty_value'; +import { getEmptyTagValue } from '../../empty_value'; +import { InvestigateInTimelineButton } from './investigate_in_timeline_button'; import { useAlertPrevalence } from '../../../containers/alerts/use_alert_prevalence'; const PrevalenceCell = React.memo( - ({ data, values, timelineId }) => { + ({ data, eventId, fieldFromBrowserField, linkValue, timelineId, values }) => { const { loading, count } = useAlertPrevalence({ field: data.field, timelineId, @@ -23,8 +24,21 @@ const PrevalenceCell = React.memo( if (loading) { return ; + } else if (typeof count === 'number') { + return ( + + {count} + + ); } else { - return {defaultToEmptyTag(count)}; + return getEmptyTagValue(); } } ); From e1ff02012a1bf6adea182b75c105d21e983ad01f Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 8 Jun 2022 13:50:58 -0700 Subject: [PATCH 09/44] [kbn/optimizer] fix --update-limits (#133945) --- scripts/build_kibana_platform_plugins.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/build_kibana_platform_plugins.js b/scripts/build_kibana_platform_plugins.js index 1a016e8b8e54f..1c1b17feae179 100644 --- a/scripts/build_kibana_platform_plugins.js +++ b/scripts/build_kibana_platform_plugins.js @@ -8,6 +8,10 @@ require('../src/setup_node_env/ensure_node_preserve_symlinks'); require('source-map-support/register'); + +var Path = require('path'); +var REPO_ROOT = require('@kbn/utils').REPO_ROOT; + require('@kbn/optimizer').runKbnOptimizerCli({ - defaultLimitsPath: require.resolve('@kbn/optimizer/limits.yml'), + defaultLimitsPath: Path.resolve(REPO_ROOT, 'packages/kbn-optimizer/limits.yml'), }); From 24560229dd8ddb113b9500072fa8077e54b317e1 Mon Sep 17 00:00:00 2001 From: Tre Date: Wed, 8 Jun 2022 22:23:59 +0100 Subject: [PATCH 10/44] [QA] Add new types to cleanStandardList fn. (#133891) Types are from: api/kibana/management/saved_objects/_allowed_types --- .../kbn_client/kbn_client_saved_objects.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index 3e8782b2a5f78..b3fd56a81029e 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -223,18 +223,31 @@ export class KbnClientSavedObjects { public async cleanStandardList(options?: { space?: string }) { // add types here const types = [ - 'search', + 'url', 'index-pattern', + 'action', + 'query', + 'alert', + 'graph-workspace', + 'tag', 'visualization', + 'canvas-element', + 'canvas-workpad', 'dashboard', + 'search', 'lens', 'map', - 'graph-workspace', - 'query', - 'tag', - 'url', - 'canvas-workpad', + 'cases', + 'uptime-dynamic-settings', + 'osquery-saved-query', + 'osquery-pack', + 'infrastructure-ui-source', + 'metrics-explorer-view', + 'inventory-view', + 'infrastructure-monitoring-log-view', + 'apm-indices', ]; + const newOptions = { types, space: options?.space }; await this.clean(newOptions); } From e02024b56a9361a8c79fec77c88459b623a20720 Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Wed, 8 Jun 2022 16:34:34 -0500 Subject: [PATCH 11/44] cleanup test/functional/fixtures/es_archiver/dashboard/legacy (#133827) * kbnArchiver es_archiver/dashboard/legacy * cleanStandardList of saved objects in init, and after * remove unused files Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../functional/apps/dashboard/group4/index.ts | 2 + .../es_archiver/dashboard/legacy/data.json.gz | Bin 2093 -> 0 bytes .../dashboard/legacy/mappings.json | 205 --------------- .../dashboard/legacy_ccs/data.json.gz | Bin 2093 -> 0 bytes .../dashboard/legacy_ccs/mappings.json | 205 --------------- .../dashboard/{ => legacy}/legacy.json | 0 .../fixtures/kbn_archiver/legacy.json | 241 ++++++++++++++++++ .../functional/page_objects/dashboard_page.ts | 10 +- 8 files changed, 246 insertions(+), 417 deletions(-) delete mode 100644 test/functional/fixtures/es_archiver/dashboard/legacy/data.json.gz delete mode 100644 test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json delete mode 100644 test/functional/fixtures/es_archiver/dashboard/legacy_ccs/data.json.gz delete mode 100644 test/functional/fixtures/es_archiver/dashboard/legacy_ccs/mappings.json rename test/functional/fixtures/kbn_archiver/dashboard/{ => legacy}/legacy.json (100%) create mode 100644 test/functional/fixtures/kbn_archiver/legacy.json diff --git a/test/functional/apps/dashboard/group4/index.ts b/test/functional/apps/dashboard/group4/index.ts index a62335c590c6d..b876690f5e2a5 100644 --- a/test/functional/apps/dashboard/group4/index.ts +++ b/test/functional/apps/dashboard/group4/index.ts @@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); async function loadLogstash() { await browser.setWindowSize(1200, 900); @@ -19,6 +20,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { async function unloadLogstash() { await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.savedObjects.cleanStandardList(); } describe('dashboard app - group 4', function () { diff --git a/test/functional/fixtures/es_archiver/dashboard/legacy/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/legacy/data.json.gz deleted file mode 100644 index c4f3ebc6b55647e74c01d684e2c5dc9758d023db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2093 zcmV+|2-5c-iwFP!000026YX1HbK|xVf4@(`(PQQ;OU~Je^>lyIbkaHdJSR7qG``V5 zcXBk}dMi)H=m!Lph^qyZ>ZY@z8#q*h^#j?x| z5(oIdjukHqB=~sAga#zp1uey>k_gD2SbS`~gPWh&eQWpH@_Ca3HUnB=`|CSOYkB)! zdwm6h;iPY5Zjh0_oQ-&fJdNf&F=WagXzyTYAt5T_QI zhL{xFv)D=#PYhpI=>!?}E3}xY#JA1IDf8*UW5Mx6c+qCTJ^O&;%7LfAXQ|J~B9+?b z@)?U$o&(x#=Ftxbo?mH?r@#}Bn}3P!&Uds@q(ugqREuzefhKl(KAq4|D?Im~$%JF_ znE3U+c8YKgr&J=S)KGH+6B_rP8vTevCaeUlk$ZR)nyqtqJ{VoF7<4$ATRQ?$-#}^$ z9uH?y74+I1G>MO9p{D%OF+9E;76z{Pj3a3Z&!+%4C=>0w$mBVeoNOr+*q)6vQ7k-c z4Wl->2`hd>D5?U+QYyrJD)xM3=3h5g!FMpI<{Ray zxb69RB|K|IJ_5MlMuYWUW(rO=&1*s5x7i`pk2^BO;AFQda0i->cx0=Nw!=G_5Gsc+ z>Do#gmKEOauBMBP3d4?XU#qMzh}N`AuDW{D!fE*oxw$aRd$ppR3a(S%SHu&Zc&!Rg zw$q>cTR70MR9k5J{4^FmxPEloW=&WYFJLN8$CkT4jo+QpgtyDy>Xl{J`L~WCx^MlG zj}01fW7fvtH7H&2KWtLDw%^$IFM2LWqAH7%cobLbL z+bkmDRrL^QrRUgM4dZ%Q#dTH+!!-z$+8w%S{*m50r0-Qi_DNWY%5c$JTAN==kvSgI~d8t0CLN zaeqK+S&*apnhTsrzsujG)v44VOWhkYt7{)t2DSV>favGH{qsNn`uo5C@zdT{%&>Dd zMJx3ON9#QNMl4!&acyeEk0sEhGRf5bJE=YAiekU2UUqJ2GUp90++8cORfI71|-e_n|F_rT(iock6FXy`9plJjp!ejl) zyb|IqT!YYEn%pqpHUln9ut7o!8LI<$#p5-$S}<_Xo+U+tm9x6{xG~eTjIY3KY|vuD z8fI~fGZmT;k`D$0+P5vjCKzc>k;%j{VJ(;d0&vPywUoIm@fId)L}$TQ>lgp!*&Ng4)&Hhbq>piB#o2-D`^ z2?I;ivD01peq4Q$v0_8bk#=6cG$wFJqBmT{0;0;;oxjvyfo7&uRo~7-ppybKC+y}g zQf8#=M>UD(?+O-kLkB(R4;}eEOL$4F92`2zYf;ki4(sPrsWRFWQ22DW%)H(JlGhx;BAPBD;;}+7vk;(NKC|2CrFSW}^eU5#+oWgp?t< zBF0;cUVR(qIuB3SI$Z^$bH_V0Qp#Djnhm9LUPU{NZ*?Z6NolLPb%%imS4d%Ro!aSB zXHJzUHn(%U1~Ui9FWv5K;R&wJV=5|5u$!gW_OZNg6>_(R~c*#-ANZM7!7UNm;0 z?!%iq)jfKa?EhgZg+@Cowk8anB|FpQ~4e=T5H4XFJ*4Vm0lkW9%YgR zpg3}yzhTC3r5|OT_jvWy{pt_L#~-};?pF$!Z#!A%;pzMC&K>X2QqW1Wt@`dxH{I!r XAMoA(FMa-Rl|KIqUk%oB{ZIe^;m0cb diff --git a/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json b/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json deleted file mode 100644 index 451369d85acd8..0000000000000 --- a/test/functional/fixtures/es_archiver/dashboard/legacy/mappings.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/es_archiver/dashboard/legacy_ccs/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/legacy_ccs/data.json.gz deleted file mode 100644 index c4f3ebc6b55647e74c01d684e2c5dc9758d023db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2093 zcmV+|2-5c-iwFP!000026YX1HbK|xVf4@(`(PQQ;OU~Je^>lyIbkaHdJSR7qG``V5 zcXBk}dMi)H=m!Lph^qyZ>ZY@z8#q*h^#j?x| z5(oIdjukHqB=~sAga#zp1uey>k_gD2SbS`~gPWh&eQWpH@_Ca3HUnB=`|CSOYkB)! zdwm6h;iPY5Zjh0_oQ-&fJdNf&F=WagXzyTYAt5T_QI zhL{xFv)D=#PYhpI=>!?}E3}xY#JA1IDf8*UW5Mx6c+qCTJ^O&;%7LfAXQ|J~B9+?b z@)?U$o&(x#=Ftxbo?mH?r@#}Bn}3P!&Uds@q(ugqREuzefhKl(KAq4|D?Im~$%JF_ znE3U+c8YKgr&J=S)KGH+6B_rP8vTevCaeUlk$ZR)nyqtqJ{VoF7<4$ATRQ?$-#}^$ z9uH?y74+I1G>MO9p{D%OF+9E;76z{Pj3a3Z&!+%4C=>0w$mBVeoNOr+*q)6vQ7k-c z4Wl->2`hd>D5?U+QYyrJD)xM3=3h5g!FMpI<{Ray zxb69RB|K|IJ_5MlMuYWUW(rO=&1*s5x7i`pk2^BO;AFQda0i->cx0=Nw!=G_5Gsc+ z>Do#gmKEOauBMBP3d4?XU#qMzh}N`AuDW{D!fE*oxw$aRd$ppR3a(S%SHu&Zc&!Rg zw$q>cTR70MR9k5J{4^FmxPEloW=&WYFJLN8$CkT4jo+QpgtyDy>Xl{J`L~WCx^MlG zj}01fW7fvtH7H&2KWtLDw%^$IFM2LWqAH7%cobLbL z+bkmDRrL^QrRUgM4dZ%Q#dTH+!!-z$+8w%S{*m50r0-Qi_DNWY%5c$JTAN==kvSgI~d8t0CLN zaeqK+S&*apnhTsrzsujG)v44VOWhkYt7{)t2DSV>favGH{qsNn`uo5C@zdT{%&>Dd zMJx3ON9#QNMl4!&acyeEk0sEhGRf5bJE=YAiekU2UUqJ2GUp90++8cORfI71|-e_n|F_rT(iock6FXy`9plJjp!ejl) zyb|IqT!YYEn%pqpHUln9ut7o!8LI<$#p5-$S}<_Xo+U+tm9x6{xG~eTjIY3KY|vuD z8fI~fGZmT;k`D$0+P5vjCKzc>k;%j{VJ(;d0&vPywUoIm@fId)L}$TQ>lgp!*&Ng4)&Hhbq>piB#o2-D`^ z2?I;ivD01peq4Q$v0_8bk#=6cG$wFJqBmT{0;0;;oxjvyfo7&uRo~7-ppybKC+y}g zQf8#=M>UD(?+O-kLkB(R4;}eEOL$4F92`2zYf;ki4(sPrsWRFWQ22DW%)H(JlGhx;BAPBD;;}+7vk;(NKC|2CrFSW}^eU5#+oWgp?t< zBF0;cUVR(qIuB3SI$Z^$bH_V0Qp#Djnhm9LUPU{NZ*?Z6NolLPb%%imS4d%Ro!aSB zXHJzUHn(%U1~Ui9FWv5K;R&wJV=5|5u$!gW_OZNg6>_(R~c*#-ANZM7!7UNm;0 z?!%iq)jfKa?EhgZg+@Cowk8anB|FpQ~4e=T5H4XFJ*4Vm0lkW9%YgR zpg3}yzhTC3r5|OT_jvWy{pt_L#~-};?pF$!Z#!A%;pzMC&K>X2QqW1Wt@`dxH{I!r XAMoA(FMa-Rl|KIqUk%oB{ZIe^;m0cb diff --git a/test/functional/fixtures/es_archiver/dashboard/legacy_ccs/mappings.json b/test/functional/fixtures/es_archiver/dashboard/legacy_ccs/mappings.json deleted file mode 100644 index 451369d85acd8..0000000000000 --- a/test/functional/fixtures/es_archiver/dashboard/legacy_ccs/mappings.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/dashboard/legacy.json b/test/functional/fixtures/kbn_archiver/dashboard/legacy/legacy.json similarity index 100% rename from test/functional/fixtures/kbn_archiver/dashboard/legacy.json rename to test/functional/fixtures/kbn_archiver/dashboard/legacy/legacy.json diff --git a/test/functional/fixtures/kbn_archiver/legacy.json b/test/functional/fixtures/kbn_archiver/legacy.json new file mode 100644 index 0000000000000..d8db4f6739edb --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/legacy.json @@ -0,0 +1,241 @@ +{ + "attributes": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "coreMigrationVersion": "8.4.0", + "id": "logstash-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzksMl0=" +} + +{ + "attributes": { + "description": "InputControl", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "title": "Visualization InputControl", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"logstash control panel\",\"type\":\"input_control_vis\",\"params\":{\"controls\":[{\"id\":\"1508761640807\",\"fieldName\":\"machine.os.raw\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_0_index_pattern\"},{\"id\":\"1508761655907\",\"fieldName\":\"bytes\",\"label\":\"\",\"type\":\"range\",\"options\":{\"decimalPlaces\":0,\"step\":512},\"indexPatternRefName\":\"control_1_index_pattern\"},{\"id\":\"1510594169532\",\"fieldName\":\"geo.dest\",\"label\":\"\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"size\":5,\"order\":\"desc\"},\"indexPatternRefName\":\"control_2_index_pattern\"}],\"updateFiltersOnChange\":false},\"aggs\":[]}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization-InputControl", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "control_0_index_pattern", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "control_1_index_pattern", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "control_2_index_pattern", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzEwLDJd" +} + +{ + "attributes": { + "description": "MetricChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization MetricChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"metric\",\"params\":{\"handleNoResults\":true,\"fontSize\":60},\"aggs\":[{\"id\":\"1\",\"type\":\"percentile_ranks\",\"schema\":\"metric\",\"params\":{\"field\":\"memory\",\"values\":[99]}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization-MetricChart", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzE2LDJd" +} + +{ + "attributes": { + "description": "PieChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization PieChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"isDonut\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"distinctColors\":true,\"legendDisplay\":\"show\",\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"memory\",\"interval\":40000,\"extended_bounds\":{}}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization-PieChart", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzE0LDJd" +} + +{ + "attributes": { + "description": "TileMap", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization TileMap", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"tile_map\",\"params\":{\"mapType\":\"Scaled Circle Markers\",\"isDesaturated\":true,\"addTooltip\":true,\"heatMaxZoom\":16,\"heatMinOpacity\":0.1,\"heatRadius\":25,\"heatBlur\":15,\"heatNormalizeData\":true,\"wms\":{\"enabled\":false,\"url\":\"https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer\",\"options\":{\"version\":\"1.3.0\",\"layers\":\"0\",\"format\":\"image/png\",\"transparent\":true,\"attribution\":\"Maps provided by USGS\",\"styles\":\"\"}}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"geohash_grid\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.coordinates\",\"autoPrecision\":true,\"precision\":2}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization-TileMap", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzEzLDJd" +} + +{ + "attributes": { + "description": "VerticalBarChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization☺ VerticalBarChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{},\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization☺-VerticalBarChart", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzEyLDJd" +} + +{ + "attributes": { + "description": "DataTable", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization☺漢字 DataTable", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"showToolbar\":true},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"histogram\",\"schema\":\"bucket\",\"params\":{\"field\":\"bytes\",\"interval\":2000,\"extended_bounds\":{}}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization☺漢字-DataTable", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzExLDJd" +} + +{ + "attributes": { + "description": "AreaChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization漢字 AreaChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{},\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization漢字-AreaChart", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzE3LDJd" +} + +{ + "attributes": { + "description": "LineChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Visualization漢字 LineChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"line\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"showCircles\":true,\"smoothLines\":false,\"interpolate\":\"linear\",\"scale\":\"linear\",\"drawLinesBetweenPoints\":true,\"radiusRatio\":9,\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{},\"row\":false,\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"extension.raw\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}" + }, + "coreMigrationVersion": "8.4.0", + "id": "Visualization漢字-LineChart", + "migrationVersion": { + "visualization": "8.3.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "version": "WzE1LDJd" +} \ No newline at end of file diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 4ba341a4d8f3f..a035e05d9a0f1 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -39,7 +39,6 @@ export class DashboardPageObject extends FtrService { private readonly listingTable = this.ctx.getService('listingTable'); private readonly elasticChart = this.ctx.getService('elasticChart'); private readonly common = this.ctx.getPageObject('common'); - private readonly esArchiver = this.ctx.getService('esArchiver'); private readonly header = this.ctx.getPageObject('header'); private readonly visualize = this.ctx.getPageObject('visualize'); private readonly discover = this.ctx.getPageObject('discover'); @@ -48,15 +47,12 @@ export class DashboardPageObject extends FtrService { : 'logstash-*'; private readonly kibanaIndex = this.config.get('esTestCluster.ccs') ? 'test/functional/fixtures/kbn_archiver/ccs/dashboard/legacy/legacy.json' - : 'test/functional/fixtures/es_archiver/dashboard/legacy'; + : 'test/functional/fixtures/kbn_archiver/dashboard/legacy/legacy.json'; async initTests({ kibanaIndex = this.kibanaIndex, defaultIndex = this.logstashIndex } = {}) { this.log.debug('load kibana index with visualizations and log data'); - if (this.config.get('esTestCluster.ccs')) { - await this.kibanaServer.importExport.load(kibanaIndex); - } else { - await this.esArchiver.loadIfNeeded(kibanaIndex); - } + await this.kibanaServer.savedObjects.cleanStandardList(); + await this.kibanaServer.importExport.load(kibanaIndex); await this.kibanaServer.uiSettings.replace({ defaultIndex }); await this.common.navigateToApp('dashboard'); } From bc591aedfa934fb2725e60d0dca07e34aa7a691b Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Wed, 8 Jun 2022 14:39:58 -0700 Subject: [PATCH 12/44] [Session View][Security Solution][8.3]Refresh button bug fix (#133935) * Fix for refresh button issue * fix for refresh button * fix for failed e2e test --- x-pack/plugins/session_view/common/constants.ts | 5 +++-- .../session_view/public/components/session_view/hooks.ts | 5 +++-- x-pack/test/session_view/basic/tests/process_events_route.ts | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/session_view/common/constants.ts b/x-pack/plugins/session_view/common/constants.ts index 2bb69bcec9fa2..3782450782ca6 100644 --- a/x-pack/plugins/session_view/common/constants.ts +++ b/x-pack/plugins/session_view/common/constants.ts @@ -20,9 +20,10 @@ export const ALERT_STATUS = { CLOSED: 'closed', }; -export const PROCESS_EVENTS_PER_PAGE = 200; -export const ALERTS_PER_PROCESS_EVENTS_PAGE = 600; +export const PROCESS_EVENTS_PER_PAGE = 500; +export const ALERTS_PER_PROCESS_EVENTS_PAGE = 1500; export const ALERTS_PER_PAGE = 100; +export const ALERTS_IN_FIRST_PAGE = 8; // when showing the count of alerts in details panel tab, if the number // exceeds ALERT_COUNT_THRESHOLD we put a + next to it, e.g 500+ diff --git a/x-pack/plugins/session_view/public/components/session_view/hooks.ts b/x-pack/plugins/session_view/public/components/session_view/hooks.ts index 02efaa6055dc2..4ed62cf32097a 100644 --- a/x-pack/plugins/session_view/public/components/session_view/hooks.ts +++ b/x-pack/plugins/session_view/public/components/session_view/hooks.ts @@ -58,8 +58,9 @@ export const useFetchSessionViewProcessEvents = ( return { events, cursor, total: res.total }; }, { - getNextPageParam: (lastPage) => { - if (lastPage.events.length >= PROCESS_EVENTS_PER_PAGE) { + getNextPageParam: (lastPage, pages) => { + const isRefetch = pages.length === 1 && jumpToCursor; + if (isRefetch || lastPage.events.length >= PROCESS_EVENTS_PER_PAGE) { return { cursor: lastPage.events[lastPage.events.length - 1]['@timestamp'], forward: true, diff --git a/x-pack/test/session_view/basic/tests/process_events_route.ts b/x-pack/test/session_view/basic/tests/process_events_route.ts index 04558df9f3bfb..f492379cb97ef 100644 --- a/x-pack/test/session_view/basic/tests/process_events_route.ts +++ b/x-pack/test/session_view/basic/tests/process_events_route.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { PROCESS_EVENTS_ROUTE, PROCESS_EVENTS_PER_PAGE, + ALERTS_IN_FIRST_PAGE, } from '@kbn/session-view-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { User } from '../../../rule_registry/common/lib/authentication/types'; @@ -54,7 +55,7 @@ export default function processEventsTests({ getService }: FtrProviderContext) { }); expect(response.status).to.be(200); expect(response.body.total).to.be(504); - expect(response.body.events.length).to.be(PROCESS_EVENTS_PER_PAGE); + expect(response.body.events.length).to.be(PROCESS_EVENTS_PER_PAGE + ALERTS_IN_FIRST_PAGE); }); it(`${PROCESS_EVENTS_ROUTE} returns a page of process events (w alerts) (paging forward)`, async () => { From 42ba236b8e390fc0ef5b3786ea810bdf1d321456 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 8 Jun 2022 14:53:36 -0700 Subject: [PATCH 13/44] [ci/screenshots] enable discovering truncated screenshot names (#133950) --- .../report_failures_to_file.ts | 5 ++++- .../functional/services/common/failure_debugging.ts | 13 ++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts index 6985ad32c0808..9336c40d35bdb 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failures_to_file.ts @@ -96,7 +96,10 @@ export function reportFailuresToFile( ); let screenshot = ''; - const screenshotName = `${failure.name.replace(/([^ a-zA-Z0-9-]+)/g, '_')}`; + const truncatedName = failure.name.replace(/([^ a-zA-Z0-9-]+)/g, '_').slice(0, 80); + const failureNameHash = createHash('sha256').update(failure.name).digest('hex'); + const screenshotName = `${truncatedName}-${failureNameHash}`; + if (screenshotsByName[screenshotName]) { try { screenshot = readFileSync(screenshotsByName[screenshotName]).toString('base64'); diff --git a/test/functional/services/common/failure_debugging.ts b/test/functional/services/common/failure_debugging.ts index 30eaafdaff64a..343036436293d 100644 --- a/test/functional/services/common/failure_debugging.ts +++ b/test/functional/services/common/failure_debugging.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { writeFile, mkdir } from 'fs'; import { promisify } from 'util'; -import Uuid from 'uuid'; +import { createHash } from 'crypto'; import del from 'del'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -49,11 +49,14 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex } async function onFailure(_: any, test: Test) { + const fullName = test.fullTitle(); + + // include a hash of the full title of the test in the filename so that even with truncation filenames are + // always unique and deterministic based on the test title + const hash = createHash('sha256').update(fullName).digest('hex'); + // Replace characters in test names which can't be used in filenames, like * - let name = test.fullTitle().replace(/([^ a-zA-Z0-9-]+)/g, '_'); - if (name.length > 100) { - name = `truncated-${name.slice(-100)}-${Uuid.v4()}`; - } + const name = `${fullName.replace(/([^ a-zA-Z0-9-]+)/g, '_').slice(0, 80)}-${hash}`; await Promise.all([screenshots.takeForFailure(name), logCurrentUrl(), savePageHtml(name)]); } From 5d6e8940dfbd20b928c14bed9b62b96bc1a52dbf Mon Sep 17 00:00:00 2001 From: Baturalp Gurdin <9674241+suchcodemuchwow@users.noreply.github.com> Date: Thu, 9 Jun 2022 00:12:39 +0200 Subject: [PATCH 14/44] exclude scalability dataset extraction from single user performance tests (#133964) --- .buildkite/pipelines/performance/daily.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.buildkite/pipelines/performance/daily.yml b/.buildkite/pipelines/performance/daily.yml index 07e73c27508a6..4e243a23f1e02 100644 --- a/.buildkite/pipelines/performance/daily.yml +++ b/.buildkite/pipelines/performance/daily.yml @@ -19,11 +19,11 @@ steps: depends_on: build key: tests - - label: ':shipit: Performance Tests dataset extraction for scalability benchmarking' - command: .buildkite/scripts/steps/functional/scalability_dataset_extraction.sh - agents: - queue: n2-2 - depends_on: tests + # - label: ':shipit: Performance Tests dataset extraction for scalability benchmarking' + # command: .buildkite/scripts/steps/functional/scalability_dataset_extraction.sh + # agents: + # queue: n2-2 + # depends_on: tests - wait: ~ continue_on_failure: true From 17c25001835ec8f35ac4069efbf9f0bebcceb7d5 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Thu, 9 Jun 2022 01:37:33 -0400 Subject: [PATCH 15/44] Fix share to space flyout sizing (#133949) --- .../components/alias_table.tsx | 32 ++++++++++--------- .../components/share_mode_control.tsx | 4 +-- .../components/share_to_space_form.tsx | 8 ++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx index 52cf55e72c996..1cb5016beb1d6 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx @@ -55,24 +55,26 @@ export const AliasTable: FunctionComponent = ({ spaces, aliasesToDisable return ( <> - + + } + color="warning" + > - } - color="warning" - > - - + - + + }> diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx index 319b8a0c98a9c..7735a94622a77 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx @@ -110,7 +110,7 @@ export const ShareModeControl = (props: Props) => { const docLink = docLinks?.links.security.kibanaPrivileges; return ( - <> + { - + ); }; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx index e5391e3c87143..658a1ca2d0980 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx @@ -7,8 +7,8 @@ import './share_to_space_form.scss'; -import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; -import React, { Fragment } from 'react'; +import { EuiCallOut, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -47,7 +47,7 @@ export const ShareToSpaceForm = (props: Props) => { onUpdate({ ...shareOptions, selectedSpaceIds }); const createCopyCallout = showCreateCopyCallout ? ( - + { - + ) : null; return ( From 9ea87308866d298bf55ce7b8a667cb89c6587e4a Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Thu, 9 Jun 2022 08:07:00 +0200 Subject: [PATCH 16/44] Introduce user profiles (#132522) Co-authored-by: Thom Heymann --- package.json | 1 + packages/kbn-optimizer/limits.yml | 2 +- renovate.json | 1 + x-pack/plugins/cloud/public/plugin.test.ts | 7 +- x-pack/plugins/cloud/public/plugin.tsx | 6 +- x-pack/plugins/cloud/server/plugin.test.ts | 70 ++ x-pack/plugins/cloud/server/plugin.ts | 4 + x-pack/plugins/security/common/index.ts | 5 + .../common/model/authenticated_user.mock.ts | 1 + .../common/model/authenticated_user.test.ts | 122 ++- .../common/model/authenticated_user.ts | 43 +- x-pack/plugins/security/common/model/index.ts | 19 +- x-pack/plugins/security/common/model/user.ts | 2 +- .../common/model/user_profile.mock.ts | 29 + .../security/common/model/user_profile.ts | 116 +++ .../account_management_app.test.ts | 81 -- .../account_management_app.test.tsx | 84 ++ .../account_management_app.ts | 50 -- .../account_management_app.tsx | 106 +++ .../account_management_page.test.tsx | 178 ++--- .../account_management_page.tsx | 111 +-- .../public/account_management/index.ts | 4 +- .../account_management/user_profile/index.ts | 11 + .../user_profile/user_profile.test.tsx | 184 +++++ .../user_profile/user_profile.tsx | 738 ++++++++++++++++++ .../user_profile_api_client.test.ts | 42 + .../user_profile/user_profile_api_client.ts | 46 ++ .../account_management/user_profile/utils.ts | 76 ++ .../public/components/form_changes.test.tsx | 68 ++ .../public/components/form_changes.tsx | 74 ++ .../public/components/form_field.test.tsx | 178 +++++ .../security/public/components/form_field.tsx | 125 +++ .../public/components/form_label.test.tsx | 44 ++ .../security/public/components/form_label.tsx | 58 ++ .../public/components/form_row.test.tsx | 44 ++ .../security/public/components/form_row.tsx | 66 ++ .../security/public/components/index.ts | 17 + .../security_api_clients_provider.ts | 30 + .../public/components/use_current_user.ts | 9 + .../public/components/user_avatar.tsx | 50 ++ x-pack/plugins/security/public/index.ts | 3 +- .../edit_user/change_password_flyout.tsx | 297 ------- .../users/edit_user/change_password_modal.tsx | 304 ++++++++ ...est.tsx => change_password_model.test.tsx} | 18 +- .../users/edit_user/edit_user_page.tsx | 4 +- .../management/users/edit_user/user_form.tsx | 2 +- .../nav_control/nav_control_component.scss | 11 - .../nav_control_component.test.tsx | 521 ++++++++----- .../nav_control/nav_control_component.tsx | 286 +++---- .../nav_control/nav_control_service.test.ts | 106 ++- .../nav_control/nav_control_service.tsx | 82 +- x-pack/plugins/security/public/plugin.tsx | 15 +- .../change_password/change_password.tsx | 0 .../change_password/change_password_async.tsx | 0 .../change_password/index.ts | 0 .../security/public/ui_api/components.tsx | 4 +- .../plugins/security/public/ui_api/index.ts | 5 +- .../personal_info/index.ts | 0 .../personal_info/personal_info.tsx | 0 .../personal_info/personal_info_async.tsx | 0 .../authentication_result.test.ts | 62 +- .../authentication/authentication_result.ts | 49 +- .../authentication_service.test.ts | 5 + .../authentication/authentication_service.ts | 7 + .../authentication/authenticator.test.ts | 115 ++- .../server/authentication/authenticator.ts | 26 +- .../authentication/providers/base.mock.ts | 1 + .../server/authentication/providers/base.ts | 10 + .../authentication/providers/basic.test.ts | 1 + .../server/authentication/providers/basic.ts | 6 +- .../authentication/providers/kerberos.test.ts | 2 + .../authentication/providers/kerberos.ts | 1 + .../authentication/providers/oidc.test.ts | 1 + .../server/authentication/providers/oidc.ts | 3 +- .../authentication/providers/pki.test.ts | 4 + .../server/authentication/providers/pki.ts | 22 +- .../authentication/providers/saml.test.ts | 51 ++ .../server/authentication/providers/saml.ts | 3 +- .../authentication/providers/token.test.ts | 6 +- .../server/authentication/providers/token.ts | 1 + .../security/server/elasticsearch/index.ts | 5 +- x-pack/plugins/security/server/mocks.ts | 1 + x-pack/plugins/security/server/plugin.test.ts | 9 + x-pack/plugins/security/server/plugin.ts | 41 + .../security/server/routes/index.mock.ts | 2 + .../plugins/security/server/routes/index.ts | 4 + .../server/routes/user_profile/get.ts | 55 ++ .../server/routes/user_profile/index.ts | 15 + .../server/routes/user_profile/update.test.ts | 150 ++++ .../server/routes/user_profile/update.ts | 62 ++ .../server/session_management/index.ts | 2 +- .../server/session_management/session.mock.ts | 1 + .../server/session_management/session.test.ts | 178 ++++- .../server/session_management/session.ts | 52 +- .../security/server/user_profile/index.ts | 13 + .../server/user_profile/user_profile_grant.ts | 28 + .../user_profile/user_profile_service.mock.ts | 17 + .../user_profile/user_profile_service.test.ts | 412 ++++++++++ .../user_profile/user_profile_service.ts | 154 ++++ .../translations/translations/fr-FR.json | 17 - .../translations/translations/ja-JP.json | 17 - .../translations/translations/zh-CN.json | 17 - x-pack/test/accessibility/apps/users.ts | 2 +- .../apis/security/basic_login.js | 2 + .../functional/apps/security/user_email.ts | 6 +- x-pack/test/functional/apps/security/users.ts | 8 +- .../page_objects/account_settings_page.ts | 30 +- .../functional/page_objects/security_page.ts | 2 +- .../tests/http_bearer/header.ts | 1 + .../tests/kerberos/kerberos_login.ts | 1 + .../login_selector/basic_functionality.ts | 1 + .../oidc/authorization_code_flow/oidc_auth.ts | 2 + .../tests/oidc/implicit_flow/oidc_auth.ts | 1 + .../tests/pki/pki_auth.ts | 2 + .../tests/saml/saml_login.ts | 1 + yarn.lock | 21 +- 116 files changed, 5053 insertions(+), 1215 deletions(-) create mode 100644 x-pack/plugins/cloud/server/plugin.test.ts create mode 100644 x-pack/plugins/security/common/model/user_profile.mock.ts create mode 100644 x-pack/plugins/security/common/model/user_profile.ts delete mode 100644 x-pack/plugins/security/public/account_management/account_management_app.test.ts create mode 100644 x-pack/plugins/security/public/account_management/account_management_app.test.tsx delete mode 100644 x-pack/plugins/security/public/account_management/account_management_app.ts create mode 100644 x-pack/plugins/security/public/account_management/account_management_app.tsx create mode 100644 x-pack/plugins/security/public/account_management/user_profile/index.ts create mode 100644 x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx create mode 100644 x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx create mode 100644 x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.test.ts create mode 100644 x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.ts create mode 100644 x-pack/plugins/security/public/account_management/user_profile/utils.ts create mode 100644 x-pack/plugins/security/public/components/form_changes.test.tsx create mode 100644 x-pack/plugins/security/public/components/form_changes.tsx create mode 100644 x-pack/plugins/security/public/components/form_field.test.tsx create mode 100644 x-pack/plugins/security/public/components/form_field.tsx create mode 100644 x-pack/plugins/security/public/components/form_label.test.tsx create mode 100644 x-pack/plugins/security/public/components/form_label.tsx create mode 100644 x-pack/plugins/security/public/components/form_row.test.tsx create mode 100644 x-pack/plugins/security/public/components/form_row.tsx create mode 100644 x-pack/plugins/security/public/components/index.ts create mode 100644 x-pack/plugins/security/public/components/security_api_clients_provider.ts create mode 100644 x-pack/plugins/security/public/components/user_avatar.tsx delete mode 100644 x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx create mode 100644 x-pack/plugins/security/public/management/users/edit_user/change_password_modal.tsx rename x-pack/plugins/security/public/management/users/edit_user/{change_password_flyout.test.tsx => change_password_model.test.tsx} (90%) delete mode 100644 x-pack/plugins/security/public/nav_control/nav_control_component.scss rename x-pack/plugins/security/public/{account_management => ui_api}/change_password/change_password.tsx (100%) rename x-pack/plugins/security/public/{account_management => ui_api}/change_password/change_password_async.tsx (100%) rename x-pack/plugins/security/public/{account_management => ui_api}/change_password/index.ts (100%) rename x-pack/plugins/security/public/{account_management => ui_api}/personal_info/index.ts (100%) rename x-pack/plugins/security/public/{account_management => ui_api}/personal_info/personal_info.tsx (100%) rename x-pack/plugins/security/public/{account_management => ui_api}/personal_info/personal_info_async.tsx (100%) create mode 100644 x-pack/plugins/security/server/routes/user_profile/get.ts create mode 100644 x-pack/plugins/security/server/routes/user_profile/index.ts create mode 100644 x-pack/plugins/security/server/routes/user_profile/update.test.ts create mode 100644 x-pack/plugins/security/server/routes/user_profile/update.ts create mode 100644 x-pack/plugins/security/server/user_profile/index.ts create mode 100644 x-pack/plugins/security/server/user_profile/user_profile_grant.ts create mode 100644 x-pack/plugins/security/server/user_profile/user_profile_service.mock.ts create mode 100644 x-pack/plugins/security/server/user_profile/user_profile_service.test.ts create mode 100644 x-pack/plugins/security/server/user_profile/user_profile_service.ts diff --git a/package.json b/package.json index fbf76e833a675..67b1bea7b75de 100644 --- a/package.json +++ b/package.json @@ -288,6 +288,7 @@ "file-saver": "^1.3.8", "file-type": "^10.9.0", "font-awesome": "4.7.0", + "formik": "^2.2.9", "fp-ts": "^2.3.1", "geojson-vt": "^3.2.1", "get-port": "^5.0.0", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 6e603698d053a..29a396e371b9d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -52,7 +52,7 @@ pageLoadAssetSize: savedObjectsTagging: 59482 savedObjectsTaggingOss: 20590 searchprofiler: 67080 - security: 95864 + security: 115240 snapshotRestore: 79032 spaces: 57868 telemetry: 51957 diff --git a/renovate.json b/renovate.json index 628eeec7c6e35..34ef1ea5bc0cf 100644 --- a/renovate.json +++ b/renovate.json @@ -115,6 +115,7 @@ "groupName": "platform security modules", "matchPackageNames": [ "node-forge", + "formik", "@types/node-forge", "require-in-the-middle", "tough-cookie", diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index 666af04b4bee6..9aed6f7f27cbb 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -170,13 +170,10 @@ describe('Cloud Plugin', () => { expect(hashId1).not.toEqual(hashId2); }); - test('user hash does not include cloudId when authenticated via Cloud SAML', async () => { + test('user hash does not include cloudId when user is an Elastic Cloud user', async () => { const { coreSetup } = await setupPlugin({ config: { id: 'cloudDeploymentId' }, - currentUserProps: { - username, - authentication_realm: { type: 'saml', name: 'cloud-saml-kibana' }, - }, + currentUserProps: { username, elastic_cloud_user: true }, }); expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled(); diff --git a/x-pack/plugins/cloud/public/plugin.tsx b/x-pack/plugins/cloud/public/plugin.tsx index 219303b2ea7bc..f3b07069ab578 100644 --- a/x-pack/plugins/cloud/public/plugin.tsx +++ b/x-pack/plugins/cloud/public/plugin.tsx @@ -262,11 +262,7 @@ export class CloudPlugin implements Plugin { name: 'cloud_user_id', context$: from(security.authc.getCurrentUser()).pipe( map((user) => { - if ( - getIsCloudEnabled(cloudId) && - user.authentication_realm?.type === 'saml' && - user.authentication_realm?.name === 'cloud-saml-kibana' - ) { + if (user.elastic_cloud_user) { // If the user is managed by ESS, use the plain username as the user ID: // The username is expected to be unique for these users, // and it matches how users are identified in the Cloud UI, so it allows us to correlate them. diff --git a/x-pack/plugins/cloud/server/plugin.test.ts b/x-pack/plugins/cloud/server/plugin.test.ts new file mode 100644 index 0000000000000..ccb0b8545fcf6 --- /dev/null +++ b/x-pack/plugins/cloud/server/plugin.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { coreMock } from '@kbn/core/server/mocks'; +import { CloudPlugin } from './plugin'; +import { config } from './config'; +import { securityMock } from '@kbn/security-plugin/server/mocks'; +import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; + +describe('Cloud Plugin', () => { + describe('#setup', () => { + describe('setupSecurity', () => { + it('properly handles missing optional Security dependency if Cloud ID is NOT set.', async () => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext(config.schema.validate({})) + ); + + expect(() => + plugin.setup(coreMock.createSetup(), { + usageCollection: usageCollectionPluginMock.createSetupContract(), + }) + ).not.toThrow(); + }); + + it('properly handles missing optional Security dependency if Cloud ID is set.', async () => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext(config.schema.validate({ id: 'my-cloud' })) + ); + + expect(() => + plugin.setup(coreMock.createSetup(), { + usageCollection: usageCollectionPluginMock.createSetupContract(), + }) + ).not.toThrow(); + }); + + it('does not notify Security plugin about Cloud environment if Cloud ID is NOT set.', async () => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext(config.schema.validate({})) + ); + + const securityDependencyMock = securityMock.createSetup(); + plugin.setup(coreMock.createSetup(), { + security: securityDependencyMock, + usageCollection: usageCollectionPluginMock.createSetupContract(), + }); + + expect(securityDependencyMock.setIsElasticCloudDeployment).not.toHaveBeenCalled(); + }); + + it('properly notifies Security plugin about Cloud environment if Cloud ID is set.', async () => { + const plugin = new CloudPlugin( + coreMock.createPluginInitializerContext(config.schema.validate({ id: 'my-cloud' })) + ); + + const securityDependencyMock = securityMock.createSetup(); + plugin.setup(coreMock.createSetup(), { + security: securityDependencyMock, + usageCollection: usageCollectionPluginMock.createSetupContract(), + }); + + expect(securityDependencyMock.setIsElasticCloudDeployment).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index 2cbb41531ecf5..8d5c38477d0cb 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -50,6 +50,10 @@ export class CloudPlugin implements Plugin { registerCloudDeploymentIdAnalyticsContext(core.analytics, this.config.id); registerCloudUsageCollector(usageCollection, { isCloudEnabled }); + if (isCloudEnabled) { + security?.setIsElasticCloudDeployment(); + } + if (this.config.full_story.enabled) { registerFullstoryRoute({ httpResources: core.http.resources, diff --git a/x-pack/plugins/security/common/index.ts b/x-pack/plugins/security/common/index.ts index 0da855b153be8..579f51426a48a 100644 --- a/x-pack/plugins/security/common/index.ts +++ b/x-pack/plugins/security/common/index.ts @@ -8,6 +8,7 @@ export type { SecurityLicense, SecurityLicenseFeatures, LoginLayout } from './licensing'; export type { AuthenticatedUser, + AuthenticatedUserProfile, AuthenticationProvider, PrivilegeDeprecationsService, PrivilegeDeprecationsRolesByFeatureIdRequest, @@ -17,6 +18,10 @@ export type { RoleKibanaPrivilege, FeaturesPrivileges, User, + UserProfile, + UserData, + UserAvatarData, + UserInfo, ApiKey, UserRealm, } from './model'; diff --git a/x-pack/plugins/security/common/model/authenticated_user.mock.ts b/x-pack/plugins/security/common/model/authenticated_user.mock.ts index cb7d64fe79786..73641d2fa5983 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.mock.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.mock.ts @@ -23,6 +23,7 @@ export function mockAuthenticatedUser(user: MockAuthenticatedUserProps = {}) { lookup_realm: { name: 'native1', type: 'native' }, authentication_provider: { type: 'basic', name: 'basic1' }, authentication_type: 'realm', + elastic_cloud_user: false, metadata: { _reserved: false }, ...user, }; diff --git a/x-pack/plugins/security/common/model/authenticated_user.test.ts b/x-pack/plugins/security/common/model/authenticated_user.test.ts index 86a976daf7bf6..4c84a951bf729 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.test.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.test.ts @@ -5,8 +5,128 @@ * 2.0. */ +import { applicationServiceMock } from '@kbn/core/public/mocks'; + import type { AuthenticatedUser } from './authenticated_user'; -import { canUserChangePassword } from './authenticated_user'; +import { + canUserChangeDetails, + canUserChangePassword, + canUserHaveProfile, + isUserAnonymous, +} from './authenticated_user'; +import { mockAuthenticatedUser } from './authenticated_user.mock'; + +describe('canUserChangeDetails', () => { + const { capabilities } = applicationServiceMock.createStartContract(); + + it('should indicate when user can change their details', () => { + expect( + canUserChangeDetails( + mockAuthenticatedUser({ + authentication_realm: { type: 'native', name: 'native1' }, + }), + { + ...capabilities, + management: { + security: { + users: true, + }, + }, + } + ) + ).toBe(true); + }); + + it('should indicate when user cannot change their details', () => { + expect( + canUserChangeDetails( + mockAuthenticatedUser({ + authentication_realm: { type: 'native', name: 'native1' }, + }), + { + ...capabilities, + management: { + security: { + users: false, + }, + }, + } + ) + ).toBe(false); + + expect( + canUserChangeDetails( + mockAuthenticatedUser({ + authentication_realm: { type: 'reserved', name: 'reserved1' }, + }), + { + ...capabilities, + management: { + security: { + users: true, + }, + }, + } + ) + ).toBe(false); + }); +}); + +describe('isUserAnonymous', () => { + it('should indicate anonymous user', () => { + expect( + isUserAnonymous( + mockAuthenticatedUser({ + authentication_provider: { type: 'anonymous', name: 'basic1' }, + }) + ) + ).toBe(true); + }); + + it('should indicate non-anonymous user', () => { + expect( + isUserAnonymous( + mockAuthenticatedUser({ + authentication_provider: { type: 'basic', name: 'basic1' }, + }) + ) + ).toBe(false); + }); +}); + +describe('canUserHaveProfile', () => { + it('anonymous users cannot have profiles', () => { + expect( + canUserHaveProfile( + mockAuthenticatedUser({ + authentication_provider: { type: 'anonymous', name: 'basic1' }, + }) + ) + ).toBe(false); + }); + + it('proxy authenticated users cannot have profiles', () => { + expect( + canUserHaveProfile( + mockAuthenticatedUser({ + authentication_provider: { type: 'http', name: '__http__' }, + }) + ) + ).toBe(false); + }); + + it('non-anonymous users that can have sessions can have profiles', () => { + for (const providerType of ['saml', 'oidc', 'basic', 'token', 'pki', 'kerberos']) { + expect( + canUserHaveProfile( + mockAuthenticatedUser({ + authentication_provider: { type: providerType, name: `${providerType}_name` }, + }) + ) + ).toBe(true); + } + }); +}); describe('#canUserChangePassword', () => { ['reserved', 'native'].forEach((realm) => { diff --git a/x-pack/plugins/security/common/model/authenticated_user.ts b/x-pack/plugins/security/common/model/authenticated_user.ts index d9fabc25df5ed..7f7e965994e4b 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.ts @@ -5,13 +5,25 @@ * 2.0. */ +import type { Capabilities } from '@kbn/core/types'; + import type { AuthenticationProvider } from './authentication_provider'; import type { User } from './user'; const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native']; +/** + * An Elasticsearch realm that was used to resolve and authenticate the user. + */ export interface UserRealm { + /** + * Arbitrary name of the security realm. + */ name: string; + + /** + * Type of the security realm (file, native, saml etc.). + */ type: string; } @@ -40,11 +52,38 @@ export interface AuthenticatedUser extends User { * @example "realm" | "api_key" | "token" | "anonymous" | "internal" */ authentication_type: string; + + /** + * Indicates whether user is authenticated via Elastic Cloud built-in SAML realm. + */ + elastic_cloud_user: boolean; +} + +export function isUserAnonymous(user: Pick) { + return user.authentication_provider.type === 'anonymous'; } -export function canUserChangePassword(user: AuthenticatedUser) { +/** + * All users are supposed to have profiles except anonymous users and users authenticated + * via authentication HTTP proxies. + * @param user Authenticated user information. + */ +export function canUserHaveProfile(user: AuthenticatedUser) { + return !isUserAnonymous(user) && user.authentication_provider.type !== 'http'; +} + +export function canUserChangePassword( + user: Pick +) { return ( REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE.includes(user.authentication_realm.type) && - user.authentication_provider.type !== 'anonymous' + !isUserAnonymous(user) ); } + +export function canUserChangeDetails( + user: Pick, + capabilities: Capabilities +) { + return user.authentication_realm.type === 'native' && capabilities.management.security.users; +} diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 84d7f261e51a7..817f3fcf84bc0 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -7,9 +7,26 @@ export type { ApiKey, ApiKeyToInvalidate, ApiKeyRoleDescriptors } from './api_key'; export type { User, EditUser } from './user'; +export type { + AuthenticatedUserProfile, + UserProfile, + UserData, + UserInfo, + UserAvatarData, +} from './user_profile'; +export { + getUserAvatarColor, + getUserAvatarInitials, + USER_AVATAR_MAX_INITIALS, +} from './user_profile'; export { getUserDisplayName } from './user'; export type { AuthenticatedUser, UserRealm } from './authenticated_user'; -export { canUserChangePassword } from './authenticated_user'; +export { + canUserChangePassword, + canUserChangeDetails, + isUserAnonymous, + canUserHaveProfile, +} from './authenticated_user'; export type { AuthenticationProvider } from './authentication_provider'; export { shouldProviderUseLoginForm } from './authentication_provider'; export type { BuiltinESPrivileges } from './builtin_es_privileges'; diff --git a/x-pack/plugins/security/common/model/user.ts b/x-pack/plugins/security/common/model/user.ts index 2bcea659699cb..0501b265d0631 100644 --- a/x-pack/plugins/security/common/model/user.ts +++ b/x-pack/plugins/security/common/model/user.ts @@ -23,6 +23,6 @@ export interface EditUser extends User { confirmPassword?: string; } -export function getUserDisplayName(user: User) { +export function getUserDisplayName(user: Pick) { return user.full_name || user.username; } diff --git a/x-pack/plugins/security/common/model/user_profile.mock.ts b/x-pack/plugins/security/common/model/user_profile.mock.ts new file mode 100644 index 0000000000000..fa6f34a1b603e --- /dev/null +++ b/x-pack/plugins/security/common/model/user_profile.mock.ts @@ -0,0 +1,29 @@ +/* + * 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 { mockAuthenticatedUser } from './authenticated_user.mock'; +import type { AuthenticatedUserProfile } from './user_profile'; + +export const userProfileMock = { + create: (userProfile: Partial = {}): AuthenticatedUserProfile => { + const user = mockAuthenticatedUser({ + username: 'some-username', + roles: [], + enabled: true, + }); + return { + uid: 'some-profile-uid', + enabled: true, + user: { + ...user, + active: true, + }, + data: {}, + ...userProfile, + }; + }, +}; diff --git a/x-pack/plugins/security/common/model/user_profile.ts b/x-pack/plugins/security/common/model/user_profile.ts new file mode 100644 index 0000000000000..dd97c5c957962 --- /dev/null +++ b/x-pack/plugins/security/common/model/user_profile.ts @@ -0,0 +1,116 @@ +/* + * 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 { VISUALIZATION_COLORS } from '@elastic/eui'; + +import type { User } from '..'; +import type { AuthenticatedUser } from './authenticated_user'; +import { getUserDisplayName } from './user'; + +/** + * User information returned in user profile. + */ +export interface UserInfo extends User { + active: boolean; +} + +/** + * Avatar stored in user profile. + */ +export interface UserAvatarData { + initials?: string; + color?: string; + imageUrl?: string; +} + +/** + * Placeholder for data stored in user profile. + */ +export type UserData = Record; + +/** + * Describes properties stored in user profile. + */ +export interface UserProfile { + /** + * Unique ID for of the user profile. + */ + uid: string; + + /** + * Indicates whether user profile is enabled or not. + */ + enabled: boolean; + + /** + * Information about the user that owns profile. + */ + user: UserInfo; + + /** + * User specific data associated with the profile. + */ + data: T; +} + +/** + * User profile enriched with session information. + */ +export interface AuthenticatedUserProfile extends UserProfile { + /** + * Information about the currently authenticated user that owns the profile. + */ + user: UserProfile['user'] & Pick; +} + +export const USER_AVATAR_FALLBACK_CODE_POINT = 97; // code point for lowercase "a" +export const USER_AVATAR_MAX_INITIALS = 2; + +/** + * Determines the color for the provided user profile. + * If a color is present on the user profile itself, then that is used. + * Otherwise, a color is provided from EUI's Visualization Colors based on the display name. + * + * @param {UserInfo} user User info + * @param {UserAvatarData} avatar User avatar + */ +export function getUserAvatarColor( + user: Pick, + avatar?: UserAvatarData +) { + if (avatar && avatar.color) { + return avatar.color; + } + + const firstCodePoint = getUserDisplayName(user).codePointAt(0) || USER_AVATAR_FALLBACK_CODE_POINT; + + return VISUALIZATION_COLORS[firstCodePoint % VISUALIZATION_COLORS.length]; +} + +/** + * Determines the initials for the provided user profile. + * If initials are present on the user profile itself, then that is used. + * Otherwise, the initials are calculated based off the words in the display name, with a max length of 2 characters. + * + * @param {UserInfo} user User info + * @param {UserAvatarData} avatar User avatar + */ +export function getUserAvatarInitials( + user: Pick, + avatar?: UserAvatarData +) { + if (avatar && avatar.initials) { + return avatar.initials; + } + + const words = getUserDisplayName(user).split(' '); + const numInitials = Math.min(USER_AVATAR_MAX_INITIALS, words.length); + + words.splice(numInitials, words.length); + + return words.map((word) => word.substring(0, 1)).join(''); +} diff --git a/x-pack/plugins/security/public/account_management/account_management_app.test.ts b/x-pack/plugins/security/public/account_management/account_management_app.test.ts deleted file mode 100644 index 3ebeefd9c945b..0000000000000 --- a/x-pack/plugins/security/public/account_management/account_management_app.test.ts +++ /dev/null @@ -1,81 +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. - */ - -jest.mock('./account_management_page'); - -import type { AppMount } from '@kbn/core/public'; -import { AppNavLinkStatus } from '@kbn/core/public'; -import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/mocks'; - -import { UserAPIClient } from '../management'; -import { securityMock } from '../mocks'; -import { accountManagementApp } from './account_management_app'; - -describe('accountManagementApp', () => { - it('properly registers application', () => { - const coreSetupMock = coreMock.createSetup(); - - accountManagementApp.create({ - application: coreSetupMock.application, - getStartServices: coreSetupMock.getStartServices, - authc: securityMock.createSetup().authc, - }); - - expect(coreSetupMock.application.register).toHaveBeenCalledTimes(1); - - const [[appRegistration]] = coreSetupMock.application.register.mock.calls; - expect(appRegistration).toEqual({ - id: 'security_account', - appRoute: '/security/account', - navLinkStatus: AppNavLinkStatus.hidden, - title: 'Account Management', - mount: expect.any(Function), - }); - }); - - it('properly sets breadcrumbs and renders application', async () => { - const coreSetupMock = coreMock.createSetup(); - const coreStartMock = coreMock.createStart(); - coreSetupMock.getStartServices.mockResolvedValue([coreStartMock, {}, {}]); - - const authcMock = securityMock.createSetup().authc; - - accountManagementApp.create({ - application: coreSetupMock.application, - getStartServices: coreSetupMock.getStartServices, - authc: authcMock, - }); - - const [[{ mount }]] = coreSetupMock.application.register.mock.calls; - const appMountParams = { - element: document.createElement('div'), - appBasePath: '', - onAppLeave: jest.fn(), - setHeaderActionMenu: jest.fn(), - history: scopedHistoryMock.create(), - theme$: themeServiceMock.createTheme$(), - }; - await (mount as AppMount)(appMountParams); - - expect(coreStartMock.chrome.setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(coreStartMock.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Account Management' }, - ]); - - const mockRenderApp = jest.requireMock('./account_management_page').renderAccountManagementPage; - expect(mockRenderApp).toHaveBeenCalledTimes(1); - expect(mockRenderApp).toHaveBeenCalledWith( - coreStartMock.i18n, - { element: appMountParams.element, theme$: appMountParams.theme$ }, - { - userAPIClient: expect.any(UserAPIClient), - authc: authcMock, - notifications: coreStartMock.notifications, - } - ); - }); -}); diff --git a/x-pack/plugins/security/public/account_management/account_management_app.test.tsx b/x-pack/plugins/security/public/account_management/account_management_app.test.tsx new file mode 100644 index 0000000000000..517b83670bd67 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/account_management_app.test.tsx @@ -0,0 +1,84 @@ +/* + * 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 { act } from '@testing-library/react'; +import { noop } from 'lodash'; + +import type { AppUnmount } from '@kbn/core/public'; +import { AppNavLinkStatus } from '@kbn/core/public'; +import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/mocks'; + +import { UserAPIClient } from '../management'; +import { securityMock } from '../mocks'; +import { accountManagementApp } from './account_management_app'; +import * as AccountManagementPageImports from './account_management_page'; +import { UserProfileAPIClient } from './user_profile'; + +const AccountManagementPageMock = jest + .spyOn(AccountManagementPageImports, 'AccountManagementPage') + .mockReturnValue(null); + +describe('accountManagementApp', () => { + it('should register application', () => { + const { authc } = securityMock.createSetup(); + const { application, getStartServices, http } = coreMock.createSetup(); + + accountManagementApp.create({ + application, + getStartServices, + authc, + securityApiClients: { + userProfiles: new UserProfileAPIClient(http), + users: new UserAPIClient(http), + }, + }); + + expect(application.register).toHaveBeenCalledTimes(1); + expect(application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'security_account', + appRoute: '/security/account', + navLinkStatus: AppNavLinkStatus.hidden, + mount: expect.any(Function), + }) + ); + }); + + it('should render AccountManagementPage on mount', async () => { + const { authc } = securityMock.createSetup(); + const { application, getStartServices, http } = coreMock.createSetup(); + getStartServices.mockResolvedValue([coreMock.createStart(), {}, {}]); + + accountManagementApp.create({ + application, + authc, + getStartServices, + securityApiClients: { + userProfiles: new UserProfileAPIClient(http), + users: new UserAPIClient(http), + }, + }); + + const [[{ mount }]] = application.register.mock.calls; + + let unmount: AppUnmount = noop; + await act(async () => { + unmount = await mount({ + element: document.createElement('div'), + appBasePath: '', + onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), + history: scopedHistoryMock.create(), + theme$: themeServiceMock.createTheme$(), + }); + }); + + expect(AccountManagementPageMock).toHaveBeenCalledTimes(1); + + unmount(); + }); +}); diff --git a/x-pack/plugins/security/public/account_management/account_management_app.ts b/x-pack/plugins/security/public/account_management/account_management_app.ts deleted file mode 100644 index 98d810805a16c..0000000000000 --- a/x-pack/plugins/security/public/account_management/account_management_app.ts +++ /dev/null @@ -1,50 +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 type { ApplicationSetup, AppMountParameters, StartServicesAccessor } from '@kbn/core/public'; -import { AppNavLinkStatus } from '@kbn/core/public'; -import { i18n } from '@kbn/i18n'; - -import type { AuthenticationServiceSetup } from '../authentication'; - -interface CreateDeps { - application: ApplicationSetup; - authc: AuthenticationServiceSetup; - getStartServices: StartServicesAccessor; -} - -export const accountManagementApp = Object.freeze({ - id: 'security_account', - create({ application, authc, getStartServices }: CreateDeps) { - const title = i18n.translate('xpack.security.account.breadcrumb', { - defaultMessage: 'Account Management', - }); - application.register({ - id: this.id, - title, - navLinkStatus: AppNavLinkStatus.hidden, - appRoute: '/security/account', - async mount({ element, theme$ }: AppMountParameters) { - const [[coreStart], { renderAccountManagementPage }, { UserAPIClient }] = await Promise.all( - [getStartServices(), import('./account_management_page'), import('../management')] - ); - - coreStart.chrome.setBreadcrumbs([{ text: title }]); - - return renderAccountManagementPage( - coreStart.i18n, - { element, theme$ }, - { - authc, - notifications: coreStart.notifications, - userAPIClient: new UserAPIClient(coreStart.http), - } - ); - }, - }); - }, -}); diff --git a/x-pack/plugins/security/public/account_management/account_management_app.tsx b/x-pack/plugins/security/public/account_management/account_management_app.tsx new file mode 100644 index 0000000000000..58e7885ca4d9d --- /dev/null +++ b/x-pack/plugins/security/public/account_management/account_management_app.tsx @@ -0,0 +1,106 @@ +/* + * 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 type { History } from 'history'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Router } from 'react-router-dom'; +import type { Observable } from 'rxjs'; + +import type { + ApplicationSetup, + AppMountParameters, + CoreStart, + CoreTheme, + StartServicesAccessor, +} from '@kbn/core/public'; +import { AppNavLinkStatus } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; + +import type { AuthenticationServiceSetup } from '../authentication'; +import type { SecurityApiClients } from '../components'; +import { AuthenticationProvider, SecurityApiClientsProvider } from '../components'; +import type { BreadcrumbsChangeHandler } from '../components/breadcrumb'; +import { BreadcrumbsProvider } from '../components/breadcrumb'; + +interface CreateDeps { + application: ApplicationSetup; + authc: AuthenticationServiceSetup; + securityApiClients: SecurityApiClients; + getStartServices: StartServicesAccessor; +} + +export const accountManagementApp = Object.freeze({ + id: 'security_account', + create({ application, authc, getStartServices, securityApiClients }: CreateDeps) { + application.register({ + id: this.id, + title: i18n.translate('xpack.security.account.breadcrumb', { + defaultMessage: 'User settings', + }), + navLinkStatus: AppNavLinkStatus.hidden, + appRoute: '/security/account', + async mount({ element, theme$, history }: AppMountParameters) { + const [[coreStart], { AccountManagementPage }] = await Promise.all([ + getStartServices(), + import('./account_management_page'), + ]); + + render( + + + , + element + ); + + return () => unmountComponentAtNode(element); + }, + }); + }, +}); + +export interface ProvidersProps { + services: CoreStart; + theme$: Observable; + history: History; + authc: AuthenticationServiceSetup; + securityApiClients: SecurityApiClients; + onChange?: BreadcrumbsChangeHandler; +} + +export const Providers: FunctionComponent = ({ + services, + theme$, + history, + authc, + securityApiClients, + onChange, + children, +}) => ( + + + + + + + {children} + + + + + + +); diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx index 976f437beaa6b..c58b3c7d30e74 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -5,142 +5,74 @@ * 2.0. */ -import { act } from '@testing-library/react'; +import { render } from '@testing-library/react'; import React from 'react'; -import { coreMock } from '@kbn/core/public/mocks'; -import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; +import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/mocks'; -import type { AuthenticatedUser } from '../../common/model'; +import type { UserData } from '../../common'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; -import { userAPIClientMock } from '../management/users/index.mock'; +import { UserAPIClient } from '../management'; import { securityMock } from '../mocks'; +import { Providers } from './account_management_app'; import { AccountManagementPage } from './account_management_page'; +import { UserProfileAPIClient } from './user_profile'; +import * as UserProfileImports from './user_profile/user_profile'; -interface Options { - withFullName?: boolean; - withEmail?: boolean; - realm?: string; -} -const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }: Options = {}) => { - return mockAuthenticatedUser({ - full_name: withFullName ? 'Casey Smith' : '', - username: 'csmith', - email: withEmail ? 'csmith@domain.com' : '', - roles: [], - authentication_realm: { - type: realm, - name: realm, - }, - lookup_realm: { - type: realm, - name: realm, - }, - }); -}; - -function getSecuritySetupMock({ currentUser }: { currentUser: AuthenticatedUser }) { - const securitySetupMock = securityMock.createSetup(); - securitySetupMock.authc.getCurrentUser.mockResolvedValue(currentUser); - return securitySetupMock; -} +const UserProfileMock = jest.spyOn(UserProfileImports, 'UserProfile'); describe('', () => { - it(`displays users full name, username, and email address`, async () => { - const user = createUser(); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( - `Settings for ${user.full_name}` - ); - expect(wrapper.find('[data-test-subj="username"]').text()).toEqual(user.username); - expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); - }); - - it(`displays username when full_name is not provided`, async () => { - const user = createUser({ withFullName: false }); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( - `Settings for ${user.username}` - ); - }); - - it(`displays a placeholder when no email address is provided`, async () => { - const user = createUser({ withEmail: false }); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('[data-test-subj="email"]').text()).toEqual('no email address'); - }); - - it(`displays change password form for users in the native realm`, async () => { - const user = createUser(); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiFieldPassword[data-test-subj="currentPassword"]')).toHaveLength(1); - expect(wrapper.find('EuiFieldPassword[data-test-subj="newPassword"]')).toHaveLength(1); + const coreStart = coreMock.createStart(); + // @ts-ignore Capabilities are marked as readonly without a way of overriding. + coreStart.application.capabilities = { + management: { + security: { + users: true, + }, + }, + }; + const theme$ = themeServiceMock.createTheme$(); + let history = scopedHistoryMock.create(); + const authc = securityMock.createSetup().authc; + + beforeEach(() => { + history = scopedHistoryMock.create(); + authc.getCurrentUser.mockClear(); + coreStart.http.delete.mockClear(); + coreStart.http.get.mockClear(); + coreStart.http.post.mockClear(); + coreStart.notifications.toasts.addDanger.mockClear(); + coreStart.notifications.toasts.addSuccess.mockClear(); }); - it(`does not display change password form for users in the saml realm`, async () => { - const user = createUser({ realm: 'saml' }); - const wrapper = mountWithIntl( - + it('should render user profile form and set breadcrumbs', async () => { + const user = mockAuthenticatedUser(); + const data: UserData = {}; + + authc.getCurrentUser.mockResolvedValue(user); + coreStart.http.get.mockResolvedValue({ user, data }); + + const { findByRole } = render( + + + ); - await act(async () => { - await nextTick(); - wrapper.update(); - }); + await findByRole('form'); - expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(0); - expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(0); + expect(UserProfileMock).toHaveBeenCalledWith({ user, data }, expect.anything()); + expect(coreStart.chrome.setBreadcrumbs).toHaveBeenLastCalledWith([ + { href: '/security/account', text: 'User settings' }, + { href: undefined, text: 'Profile' }, + ]); }); }); diff --git a/x-pack/plugins/security/public/account_management/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx index 5eb396204b6e8..6b5f9c033422c 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.tsx @@ -5,80 +5,51 @@ * 2.0. */ -import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; -import ReactDOM from 'react-dom'; - -import type { AppMountParameters, CoreStart, NotificationsStart } from '@kbn/core/public'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import type { PublicMethodsOf } from '@kbn/utility-types'; - -import type { AuthenticatedUser } from '../../common/model'; -import { getUserDisplayName } from '../../common/model'; -import type { AuthenticationServiceSetup } from '../authentication'; -import type { UserAPIClient } from '../management'; -import { ChangePassword } from './change_password'; -import { PersonalInfo } from './personal_info'; - -interface Props { - authc: AuthenticationServiceSetup; - userAPIClient: PublicMethodsOf; - notifications: NotificationsStart; -} - -export const AccountManagementPage = ({ userAPIClient, authc, notifications }: Props) => { - const [currentUser, setCurrentUser] = useState(null); - useEffect(() => { - authc.getCurrentUser().then(setCurrentUser); - }, [authc]); +import { EuiEmptyPrompt } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import type { CoreStart } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +import type { UserAvatarData } from '../../common'; +import { canUserHaveProfile } from '../../common/model'; +import { useCurrentUser, useUserProfile } from '../components'; +import { Breadcrumb } from '../components/breadcrumb'; +import { UserProfile } from './user_profile'; + +export const AccountManagementPage: FunctionComponent = () => { + const { services } = useKibana(); + + const currentUser = useCurrentUser(); + const userProfile = useUserProfile<{ avatar: UserAvatarData }>('avatar'); + + // If we fail to load profile, we treat it as a failure _only_ if user is supposed + // to have a profile. For example, anonymous and users authenticated via + // authentication proxies don't have profiles. + const profileLoadError = + userProfile.error && currentUser.value && canUserHaveProfile(currentUser.value) + ? userProfile.error + : undefined; + + const error = currentUser.error || profileLoadError; + if (error) { + return {error.message}} />; + } - if (!currentUser) { + if (!currentUser.value || (canUserHaveProfile(currentUser.value) && !userProfile.value)) { return null; } return ( - - - - -

- {getUserDisplayName(currentUser)} }} - /> -

-
- - - - - - -
-
-
+ + + ); }; - -export function renderAccountManagementPage( - i18nStart: CoreStart['i18n'], - { element, theme$ }: Pick, - props: Props -) { - ReactDOM.render( - - - - - , - element - ); - - return () => ReactDOM.unmountComponentAtNode(element); -} diff --git a/x-pack/plugins/security/public/account_management/index.ts b/x-pack/plugins/security/public/account_management/index.ts index 2d1045723a6e1..a78746541e5a8 100644 --- a/x-pack/plugins/security/public/account_management/index.ts +++ b/x-pack/plugins/security/public/account_management/index.ts @@ -6,6 +6,4 @@ */ export { accountManagementApp } from './account_management_app'; - -export type { ChangePasswordProps } from './change_password'; -export type { PersonalInfoProps } from './personal_info'; +export { UserProfileAPIClient } from './user_profile'; diff --git a/x-pack/plugins/security/public/account_management/user_profile/index.ts b/x-pack/plugins/security/public/account_management/user_profile/index.ts new file mode 100644 index 0000000000000..17eda36459ad5 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/user_profile/index.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export { UserProfile } from './user_profile'; + +export type { UserProfileProps, UserProfileFormValues } from './user_profile'; +export { UserProfileAPIClient } from './user_profile_api_client'; diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx new file mode 100644 index 0000000000000..c0c90c6421ab2 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.test.tsx @@ -0,0 +1,184 @@ +/* + * 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 { act, renderHook } from '@testing-library/react-hooks'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/mocks'; + +import { UserProfileAPIClient } from '..'; +import type { UserData } from '../../../common'; +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { UserAPIClient } from '../../management'; +import { securityMock } from '../../mocks'; +import { Providers } from '../account_management_app'; +import { useUserProfileForm } from './user_profile'; + +const user = mockAuthenticatedUser(); +const coreStart = coreMock.createStart(); +const theme$ = themeServiceMock.createTheme$(); +let history = scopedHistoryMock.create(); +const authc = securityMock.createSetup().authc; + +const wrapper: FunctionComponent = ({ children }) => ( + + {children} + +); + +describe('useUserProfileForm', () => { + beforeEach(() => { + history = scopedHistoryMock.create(); + authc.getCurrentUser.mockReset(); + // @ts-ignore Capabilities are marked as readonly without a way of overriding. + coreStart.application.capabilities = { + management: { + security: { + users: true, + }, + }, + }; + coreStart.http.delete.mockReset(); + coreStart.http.get.mockReset(); + coreStart.http.post.mockReset().mockResolvedValue(undefined); + coreStart.notifications.toasts.addDanger.mockReset(); + coreStart.notifications.toasts.addSuccess.mockReset(); + }); + + it('should initialise form with values from user profile', () => { + const data: UserData = { + avatar: {}, + }; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + expect(result.current.values).toMatchInlineSnapshot(` + Object { + "avatarType": "initials", + "data": Object { + "avatar": Object { + "color": "#D36086", + "imageUrl": "", + "initials": "fn", + }, + }, + "user": Object { + "email": "email", + "full_name": "full name", + }, + } + `); + }); + + it('should initialise form with values from user avatar if present', () => { + const data: UserData = { + avatar: { + imageUrl: 'avatar.png', + }, + }; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + expect(result.current.values).toEqual( + expect.objectContaining({ + avatarType: 'image', + data: expect.objectContaining({ + avatar: expect.objectContaining({ + imageUrl: 'avatar.png', + }), + }), + }) + ); + }); + + it('should update initials when full name changes', async () => { + const data: UserData = {}; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + await act(async () => { + await result.current.setFieldValue('user.full_name', 'Another Name'); + }); + + expect(result.current.values.user.full_name).toEqual('Another Name'); + expect(result.current.values.data?.avatar.initials).toEqual('AN'); + }); + + it('should save user and user profile when submitting form', async () => { + const data: UserData = {}; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + await act(async () => { + await result.current.submitForm(); + }); + + expect(coreStart.http.post).toHaveBeenCalledTimes(2); + }); + + it("should save user profile only when user details can't be updated", async () => { + // @ts-ignore Capabilities are marked as readonly without a way of overriding. + coreStart.application.capabilities = { + management: { + security: { + users: false, + }, + }, + }; + + const data: UserData = {}; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + await act(async () => { + await result.current.submitForm(); + }); + + expect(coreStart.http.post).toHaveBeenCalledTimes(1); + }); + + it('should add toast after submitting form successfully', async () => { + const data: UserData = {}; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + await act(async () => { + await result.current.submitForm(); + }); + + expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledTimes(1); + }); + + it('should add toast after submitting form failed', async () => { + const data: UserData = {}; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + coreStart.http.post.mockRejectedValue(new Error('Error')); + + await act(async () => { + await result.current.submitForm(); + }); + + expect(coreStart.notifications.toasts.addError).toHaveBeenCalledTimes(1); + }); + + it('should set initial values to current values after submitting form successfully', async () => { + const data: UserData = {}; + const { result } = renderHook(() => useUserProfileForm({ user, data }), { wrapper }); + + await act(async () => { + await result.current.setFieldValue('user.full_name', 'Another Name'); + await result.current.submitForm(); + }); + + expect(result.current.initialValues.user.full_name).toEqual('Another Name'); + }); +}); diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx new file mode 100644 index 0000000000000..f0371cc8d868b --- /dev/null +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx @@ -0,0 +1,738 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiButtonGroup, + EuiColorPicker, + EuiDescribedFormGroup, + EuiDescriptionList, + EuiFilePicker, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiIconTip, + EuiPageTemplate, + EuiSpacer, + EuiText, + useEuiTheme, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { Form, FormikProvider, useFormik, useFormikContext } from 'formik'; +import type { FunctionComponent } from 'react'; +import React, { useRef, useState } from 'react'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; + +import type { CoreStart } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +import type { AuthenticatedUser, UserAvatarData } from '../../../common'; +import { + canUserChangeDetails, + canUserChangePassword, + getUserAvatarColor, + getUserAvatarInitials, +} from '../../../common/model'; +import { UserAvatar, useSecurityApiClients } from '../../components'; +import { Breadcrumb } from '../../components/breadcrumb'; +import { + FormChangesProvider, + useFormChanges, + useFormChangesContext, +} from '../../components/form_changes'; +import { FormField } from '../../components/form_field'; +import { FormLabel } from '../../components/form_label'; +import { FormRow, OptionalText } from '../../components/form_row'; +import { ChangePasswordModal } from '../../management/users/edit_user/change_password_modal'; +import { isUserReserved } from '../../management/users/user_utils'; +import { createImageHandler, getRandomColor, IMAGE_FILE_TYPES, VALID_HEX_COLOR } from './utils'; + +export interface UserProfileProps { + user: AuthenticatedUser; + data?: { + avatar?: UserAvatarData; + }; +} + +export interface UserProfileFormValues { + user: { + full_name: string; + email: string; + }; + data?: { + avatar: { + initials: string; + color: string; + imageUrl: string; + }; + }; + avatarType: 'initials' | 'image'; +} + +function UserDetailsEditor({ user }: { user: AuthenticatedUser }) { + const { services } = useKibana(); + + const canChangeDetails = canUserChangeDetails(user, services.application.capabilities); + if (!canChangeDetails) { + return null; + } + + return ( + + + + } + description={ + + } + > + + + + } + labelAppend={} + fullWidth + > + + + + + + + } + labelAppend={} + fullWidth + > + + + + ); +} + +function UserAvatarEditor({ + user, + formik, +}: { + user: AuthenticatedUser; + formik: ReturnType; +}) { + const { euiTheme } = useEuiTheme(); + if (!formik.values.data) { + return null; + } + + const isReservedUser = isUserReserved(user); + return ( + + + + } + description={ + + } + > + + + {formik.values.avatarType === 'image' && !formik.values.data.avatar.imageUrl ? ( + + ) : ( + + )} + + + + + + } + fullWidth + > + + ), + }, + { + id: 'image', + label: ( + + ), + iconType: 'image', + }, + ]} + onChange={(id: string) => formik.setFieldValue('avatarType', id)} + isFullWidth + /> + + + + + + {formik.values.avatarType === 'image' ? ( + + + + } + fullWidth + > + + ) : ( + + ) + } + onChange={createImageHandler((imageUrl) => { + formik.setFieldValue('data.avatar.imageUrl', imageUrl ?? ''); + })} + validate={{ + required: i18n.translate( + 'xpack.security.accountManagement.userProfile.imageUrlRequiredError', + { defaultMessage: 'Upload an image.' } + ), + }} + accept={IMAGE_FILE_TYPES.join(',')} + display="default" + fullWidth + /> + + ) : ( + + + + + + } + fullWidth + > + + + + + + + + } + labelAppend={ + !isReservedUser ? ( + formik.setFieldValue('data.avatar.color', getRandomColor())} + size="xs" + flush="right" + style={{ height: euiTheme.base }} + > + + + ) : null + } + fullWidth + > + { + formik.setFieldValue('data.avatar.color', value); + }} + fullWidth + /> + + + + )} + + ); +} + +function UserPasswordEditor({ + user, + onShowPasswordForm, +}: { + user: AuthenticatedUser; + onShowPasswordForm: () => void; +}) { + const canChangePassword = canUserChangePassword(user); + if (!canChangePassword) { + return null; + } + + return ( + + + + } + description={ + + } + > + + } + fullWidth + > + + + + + + ); +} + +export const UserProfile: FunctionComponent = ({ user, data }) => { + const { euiTheme } = useEuiTheme(); + const { services } = useKibana(); + const formik = useUserProfileForm({ user, data }); + const formChanges = useFormChanges(); + const titleId = useGeneratedHtmlId(); + const [showChangePasswordForm, setShowChangePasswordForm] = useState(false); + + const canChangeDetails = canUserChangeDetails(user, services.application.capabilities); + + const rightSideItems = [ + { + title: ( + + ), + description: user.username as string | undefined, + helpText: ( + + ), + testSubj: 'username', + }, + ]; + + if (!canChangeDetails) { + rightSideItems.push({ + title: ( + + ), + description: user.full_name, + helpText: ( + + ), + testSubj: 'full_name', + }); + + rightSideItems.push({ + title: ( + + ), + description: user.email, + helpText: ( + + ), + testSubj: 'email', + }); + } + + return ( + + + + {showChangePasswordForm ? ( + setShowChangePasswordForm(false)} + onSuccess={() => setShowChangePasswordForm(false)} + /> + ) : null} + + + ), + pageTitleProps: { id: titleId }, + rightSideItems: rightSideItems.reverse().map((item) => ( + + + {item.title} + + + + + + ), + description: ( + + {item.description || ( + + + + )} + + ), + }, + ]} + compressed + /> + )), + }} + bottomBar={formChanges.count > 0 ? : null} + bottomBarProps={{ paddingSize: 'm', position: 'fixed' }} + restrictWidth={1000} + > +
+ + + setShowChangePasswordForm(true)} + /> + + +
+
+
+
+ ); +}; + +export function useUserProfileForm({ user, data }: UserProfileProps) { + const { services } = useKibana(); + const { userProfiles, users } = useSecurityApiClients(); + + const [initialValues, resetInitialValues] = useState({ + user: { + full_name: user.full_name || '', + email: user.email || '', + }, + data: data + ? { + avatar: { + initials: data.avatar?.initials || getUserAvatarInitials(user), + color: data.avatar?.color || getUserAvatarColor(user), + imageUrl: data.avatar?.imageUrl || '', + }, + } + : undefined, + avatarType: data?.avatar?.imageUrl ? 'image' : 'initials', + }); + + const [validateOnBlurOrChange, setValidateOnBlurOrChange] = useState(false); + const formik = useFormik({ + onSubmit: async (values) => { + const submitActions = []; + if (canUserChangeDetails(user, services.application.capabilities)) { + submitActions.push( + users.saveUser({ + username: user.username, + roles: user.roles, + enabled: user.enabled, + full_name: values.user.full_name, + email: values.user.email, + }) + ); + } + + // Update profile only if it's available for the current user. + if (values.data) { + submitActions.push( + userProfiles.update( + values.avatarType === 'image' + ? values.data + : { ...values.data, avatar: { ...values.data.avatar, imageUrl: null } } + ) + ); + } + + if (submitActions.length === 0) { + return; + } + + try { + await Promise.all(submitActions); + } catch (error) { + services.notifications.toasts.addError(error, { + title: i18n.translate('xpack.security.accountManagement.userProfile.submitErrorTitle', { + defaultMessage: "Couldn't update profile", + }), + }); + return; + } + + resetInitialValues(values); + services.notifications.toasts.addSuccess( + i18n.translate('xpack.security.accountManagement.userProfile.submitSuccessTitle', { + defaultMessage: 'Profile updated', + }) + ); + }, + initialValues, + enableReinitialize: true, + validateOnBlur: validateOnBlurOrChange, + validateOnChange: validateOnBlurOrChange, + }); + + // We perform _the first_ validation only when the user submits the form to make UX less annoying. But after the user + // submits the form, the validation model changes to on blur/change (as the user's mindset has changed from completing + // the form to correcting the form). + if (formik.submitCount > 0 && !validateOnBlurOrChange) { + setValidateOnBlurOrChange(true); + } else if (formik.submitCount === 0 && validateOnBlurOrChange) { + setValidateOnBlurOrChange(false); + } + + const customAvatarInitials = useRef( + !!data?.avatar?.initials && data.avatar?.initials !== getUserAvatarInitials(user) + ); + + useUpdateEffect(() => { + if (!customAvatarInitials.current) { + const defaultInitials = getUserAvatarInitials({ + username: user.username, + full_name: formik.values.user.full_name, + }); + formik.setFieldValue('data.avatar.initials', defaultInitials); + } + }, [formik.values.user.full_name]); + + useUpdateEffect(() => { + if (!customAvatarInitials.current && formik.values.data) { + const defaultInitials = getUserAvatarInitials({ + username: user.username, + full_name: formik.values.user.full_name, + }); + customAvatarInitials.current = formik.values.data.avatar.initials !== defaultInitials; + } + }, [formik.values.data?.avatar.initials]); + + return formik; +} + +export const SaveChangesBottomBar: FunctionComponent = () => { + const formik = useFormikContext(); + const { count } = useFormChangesContext(); + + return ( + + + + + + + + + + + + + + + + + + 0 && !formik.isValid} + color="success" + iconType="save" + fill + > + + + + + ); +}; diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.test.ts b/x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.test.ts new file mode 100644 index 0000000000000..b9cd558eb08cd --- /dev/null +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; + +import { UserProfileAPIClient } from './user_profile_api_client'; + +describe('UserProfileAPIClient', () => { + let coreStart: ReturnType; + let apiClient: UserProfileAPIClient; + beforeEach(() => { + coreStart = coreMock.createStart(); + coreStart.http.post.mockResolvedValue(undefined); + + apiClient = new UserProfileAPIClient(coreStart.http); + }); + + it('should get user profile without retrieving any user data', async () => { + await apiClient.get(); + expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security/user_profile', { + query: { data: undefined }, + }); + }); + + it('should get user profile and user data', async () => { + await apiClient.get('*'); + expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security/user_profile', { + query: { data: '*' }, + }); + }); + + it('should update user data', async () => { + await apiClient.update({ avatar: { imageUrl: 'avatar.png' } }); + expect(coreStart.http.post).toHaveBeenCalledWith('/internal/security/user_profile/_data', { + body: '{"avatar":{"imageUrl":"avatar.png"}}', + }); + }); +}); diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.ts b/x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.ts new file mode 100644 index 0000000000000..7c358fd4d3513 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile_api_client.ts @@ -0,0 +1,46 @@ +/* + * 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 type { Observable } from 'rxjs'; +import { Subject } from 'rxjs'; + +import type { HttpStart } from '@kbn/core/public'; + +import type { AuthenticatedUserProfile, UserData } from '../../../common'; + +const USER_PROFILE_URL = '/internal/security/user_profile'; + +export class UserProfileAPIClient { + private readonly internalDataUpdates$: Subject = new Subject(); + + /** + * Emits event whenever user profile is changed by the user. + */ + public readonly dataUpdates$: Observable = this.internalDataUpdates$.asObservable(); + + constructor(private readonly http: HttpStart) {} + + /** + * Retrieves the user profile of the current user. + * @param dataPath By default `get()` returns user information, but does not return any user data. The optional "dataPath" parameter can be used to return personal data for this user. + */ + public get(dataPath?: string) { + return this.http.get>(USER_PROFILE_URL, { + query: { data: dataPath }, + }); + } + + /** + * Updates user profile data of the current user. + * @param data Application data to be written (merged with existing data). + */ + public update(data: T) { + return this.http.post(`${USER_PROFILE_URL}/_data`, { body: JSON.stringify(data) }).then(() => { + this.internalDataUpdates$.next(data); + }); + } +} diff --git a/x-pack/plugins/security/public/account_management/user_profile/utils.ts b/x-pack/plugins/security/public/account_management/user_profile/utils.ts new file mode 100644 index 0000000000000..fd15350288493 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/user_profile/utils.ts @@ -0,0 +1,76 @@ +/* + * 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. + */ + +export const IMAGE_FILE_TYPES = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif']; +export const MAX_IMAGE_SIZE = 64; + +export function readFile(data: File) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = reject; + reader.readAsDataURL(data); + }); +} + +export function resizeImage(imageUrl: string, maxSize: number) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => { + if (image.width <= maxSize && image.height <= maxSize) { + return resolve(imageUrl); + } + + try { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (context) { + if (image.width >= image.height) { + canvas.width = maxSize; + canvas.height = Math.floor((image.height * maxSize) / image.width); + } else { + canvas.height = maxSize; + canvas.width = Math.floor((image.width * maxSize) / image.height); + } + context.drawImage(image, 0, 0, canvas.width, canvas.height); + const resizedDataUrl = canvas.toDataURL(); + return resolve(resizedDataUrl); + } + } catch (error) { + return reject(error); + } + + return reject(); + }; + image.onerror = reject; + image.src = imageUrl; + }); +} + +export function createImageHandler(callback: (imageUrl: string | undefined) => void) { + return async (files: FileList | null) => { + if (!files || !files.length) { + callback(undefined); + return; + } + const file = files[0]; + if (IMAGE_FILE_TYPES.indexOf(file.type) !== -1) { + const imageUrl = await readFile(file); + const resizedImageUrl = await resizeImage(imageUrl, MAX_IMAGE_SIZE); + callback(resizedImageUrl); + } + }; +} + +/** + * Returns the hex representation of a random color (e.g `#F1B7E2`) + */ +export function getRandomColor() { + return '#' + String(Math.floor(Math.random() * 0xffffff).toString(16)).padStart(6, '0'); +} + +export const VALID_HEX_COLOR = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i; diff --git a/x-pack/plugins/security/public/components/form_changes.test.tsx b/x-pack/plugins/security/public/components/form_changes.test.tsx new file mode 100644 index 0000000000000..3223bb727ddfb --- /dev/null +++ b/x-pack/plugins/security/public/components/form_changes.test.tsx @@ -0,0 +1,68 @@ +/* + * 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 { act, renderHook } from '@testing-library/react-hooks'; + +import type { RevertFunction } from './form_changes'; +import { useFormChanges } from './form_changes'; + +describe('useFormChanges', () => { + it('should return correct contract', () => { + const { result } = renderHook(useFormChanges); + + expect(result.current).toEqual({ + count: 0, + report: expect.any(Function), + }); + }); + + it('should increase count when field changes', () => { + const { result } = renderHook(useFormChanges); + + expect(result.current.count).toEqual(0); + + act(() => { + result.current.report(false); + }); + + expect(result.current.count).toEqual(1); + }); + + it('should decrease count when field changes back', () => { + const { result } = renderHook(useFormChanges); + + expect(result.current.count).toEqual(0); + + let revert: RevertFunction | undefined; + act(() => { + revert = result.current.report(false); + }); + + expect(revert).not.toBeUndefined(); + expect(result.current.count).toEqual(1); + + act(() => { + revert!(); + }); + + expect(result.current.count).toEqual(0); + }); + + it('should not increase count when field remains unchanged', () => { + const { result } = renderHook(useFormChanges); + + expect(result.current.count).toEqual(0); + + let revert: RevertFunction | undefined; + act(() => { + revert = result.current.report(true); + }); + + expect(revert).toBeUndefined(); + expect(result.current.count).toEqual(0); + }); +}); diff --git a/x-pack/plugins/security/public/components/form_changes.tsx b/x-pack/plugins/security/public/components/form_changes.tsx new file mode 100644 index 0000000000000..c2724e1d193fe --- /dev/null +++ b/x-pack/plugins/security/public/components/form_changes.tsx @@ -0,0 +1,74 @@ +/* + * 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 { createContext, useContext, useState } from 'react'; + +export interface FormChangesProps { + /** + * Number of fields rendered on the page that have changed. + */ + count: number; + + /** + * Callback function used by a form field to indicate whether its current value is different to its initial value. + * + * @example + * ``` + * const { report } = useFormChangesContext(); + * const isEqual = formik.values.email === formik.initialValues.email; + * + * useEffect(() => report(isEqual), [isEqual]); + * ``` + */ + report: ReportFunction; +} + +export type ReportFunction = (isEqual: boolean) => undefined | RevertFunction; +export type RevertFunction = () => void; + +/** + * Custom React hook that allows tracking changes within a form. + * + * @example + * ``` + * const { count } = useFormChanges(); // Form has {count} unsaved changes + * ``` + */ +export const useFormChanges = (): FormChangesProps => { + const [count, setCount] = useState(0); + + return { + count, + report: (isEqual) => { + if (!isEqual) { + setCount((c) => c + 1); + return () => setCount((c) => c - 1); + } + }, + }; +}; + +const FormChangesContext = createContext(undefined); + +export const FormChangesProvider = FormChangesContext.Provider; + +/** + * Custom React hook that returns all @see FormChangesProps state from context. + * + * @throws Error if called within a component that isn't a child of a `` component. + */ +export function useFormChangesContext() { + const value = useContext(FormChangesContext); + + if (!value) { + throw new Error( + 'FormChanges context is undefined, please verify you are calling useFormChangesContext() as child of a component.' + ); + } + + return value; +} diff --git a/x-pack/plugins/security/public/components/form_field.test.tsx b/x-pack/plugins/security/public/components/form_field.test.tsx new file mode 100644 index 0000000000000..0abb7c82a833e --- /dev/null +++ b/x-pack/plugins/security/public/components/form_field.test.tsx @@ -0,0 +1,178 @@ +/* + * 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 { EuiFieldNumber, EuiFieldText } from '@elastic/eui'; +import { mount } from 'enzyme'; +import { Formik } from 'formik'; +import React from 'react'; + +import { createFieldValidator, FormField } from './form_field'; + +const onSubmit = jest.fn(); + +describe('FormField', () => { + it('should render text field by default', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.exists(EuiFieldText)).toEqual(true); + }); + + it('should render custom component if specified', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.exists(EuiFieldNumber)).toEqual(true); + }); + + it('should render component with correct field props and event handlers', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(EuiFieldText).props()).toEqual( + expect.objectContaining({ + name: 'email', + value: 'mail@example.com', + onChange: expect.any(Function), + onBlur: expect.any(Function), + }) + ); + }); + + it('should mark as invalid if field has errors and has been touched', () => { + const assertions = [ + { error: 'Error', touched: true, isInvalid: true }, + { error: 'Error', touched: false, isInvalid: false }, + { error: undefined, touched: true, isInvalid: false }, + ]; + assertions.forEach(({ error, touched, isInvalid }) => { + const wrapper = mount( + + + + ); + expect(wrapper.find(EuiFieldText).props()).toEqual(expect.objectContaining({ isInvalid })); + }); + }); +}); + +describe('createFieldValidator', () => { + it('should validate required field', () => { + const validate = createFieldValidator({ + required: 'Error', + }); + + expect(validate(undefined)).toEqual('Error'); + expect(validate(null)).toEqual('Error'); + expect(validate('')).toEqual('Error'); + + expect(validate(0)).toBeUndefined(); + expect(validate(1)).toBeUndefined(); + expect(validate('a')).toBeUndefined(); + expect(validate({})).toBeUndefined(); + expect(validate([])).toBeUndefined(); + }); + + it('should validate field pattern', () => { + const validate = createFieldValidator({ + pattern: { + value: /^[a-z]{2}$/, + message: 'Error', + }, + }); + + expect(validate(undefined)).toEqual('Error'); + expect(validate(null)).toEqual('Error'); + expect(validate(0)).toEqual('Error'); + expect(validate(1)).toEqual('Error'); + expect(validate('a')).toEqual('Error'); + + expect(validate('ab')).toBeUndefined(); + }); + + it('should validate minimum length ', () => { + const validate = createFieldValidator({ + minLength: { + value: 2, + message: 'Error', + }, + }); + + expect(validate(undefined)).toEqual('Error'); + expect(validate(null)).toEqual('Error'); + expect(validate('a')).toEqual('Error'); + expect(validate([0])).toEqual('Error'); + + expect(validate('ab')).toBeUndefined(); + expect(validate([0, 1])).toBeUndefined(); + }); + + it('should validate maximum length', () => { + const validate = createFieldValidator({ + maxLength: { + value: 2, + message: 'Error', + }, + }); + + expect(validate('abc')).toEqual('Error'); + expect(validate([0, 1, 3])).toEqual('Error'); + + expect(validate(undefined)).toBeUndefined(); + expect(validate(null)).toBeUndefined(); + expect(validate('ab')).toBeUndefined(); + expect(validate([0, 1])).toBeUndefined(); + }); + + it('should validate minimum value', () => { + const validate = createFieldValidator({ + min: { + value: 2, + message: 'Error', + }, + }); + + expect(validate(undefined)).toEqual('Error'); + expect(validate(null)).toEqual('Error'); + expect(validate(1)).toEqual('Error'); + expect(validate('1')).toEqual('Error'); + + expect(validate(2)).toBeUndefined(); + expect(validate('2')).toBeUndefined(); + }); + + it('should validate maximum value', () => { + const validate = createFieldValidator({ + max: { + value: 2, + message: 'Error', + }, + }); + + expect(validate(undefined)).toEqual('Error'); + expect(validate(3)).toEqual('Error'); + expect(validate('3')).toEqual('Error'); + + expect(validate(null)).toBeUndefined(); + expect(validate(2)).toBeUndefined(); + expect(validate('2')).toBeUndefined(); + }); +}); diff --git a/x-pack/plugins/security/public/components/form_field.tsx b/x-pack/plugins/security/public/components/form_field.tsx new file mode 100644 index 0000000000000..6e223f067c99b --- /dev/null +++ b/x-pack/plugins/security/public/components/form_field.tsx @@ -0,0 +1,125 @@ +/* + * 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 { EuiFieldText } from '@elastic/eui'; +import { useField } from 'formik'; +import type { FieldValidator } from 'formik'; +import type { ComponentPropsWithoutRef, ElementType } from 'react'; +import React from 'react'; + +export interface FormFieldProps { + as?: T; + name: string; + validate?: FieldValidator | ValidateOptions; +} + +/** + * Polymorphic component that renders a form field with all state required for inline validation. + * + * @example Text field with validation rule: + * ```typescript + * + * + * + * ``` + * + * @example Color picker using non-standard value prop and change handler: + * ```typescript + * + * formik.setFieldValue('color', value)} + * /> + * + * ``` + * + * @throws Error if not a child of a `` component. + */ +export function FormField({ + as, + validate, + onBlur, + ...rest +}: FormFieldProps & Omit, keyof FormFieldProps>) { + const Component = as || EuiFieldText; + + const [field, meta, helpers] = useField({ + name: rest.name, + validate: typeof validate === 'object' ? createFieldValidator(validate) : validate, + }); + + return ( + { + helpers.setTouched(true); // Marking as touched manually here since some EUI components don't pass on the native blur event which is required by `field.onBlur()`. + onBlur?.(event); + }} + /> + ); +} + +export interface ValidateOptions { + required?: string; + pattern?: { + value: RegExp; + message: string; + }; + minLength?: { + value: number; + message: string; + }; + maxLength?: { + value: number; + message: string; + }; + min?: { + value: number; + message: string; + }; + max?: { + value: number; + message: string; + }; +} + +export function createFieldValidator(options: ValidateOptions): FieldValidator { + return (value: any) => { + if (options.required && typeof value !== 'number' && !value) { + return options.required; + } + if (options.pattern && !options.pattern.value.test(value)) { + return options.pattern.message; + } + if ( + options.minLength && + (!value || + ((typeof value === 'object' || typeof value === 'string') && + value.length < options.minLength.value)) + ) { + return options.minLength.message; + } + if ( + options.maxLength && + value && + (typeof value === 'object' || typeof value === 'string') && + value.length > options.maxLength.value + ) { + return options.maxLength.message; + } + if (options.min && (isNaN(value) || value < options.min.value)) { + return options.min.message; + } + if (options.max && (isNaN(value) || value > options.max.value)) { + return options.max.message; + } + }; +} diff --git a/x-pack/plugins/security/public/components/form_label.test.tsx b/x-pack/plugins/security/public/components/form_label.test.tsx new file mode 100644 index 0000000000000..3f3ef72101e7a --- /dev/null +++ b/x-pack/plugins/security/public/components/form_label.test.tsx @@ -0,0 +1,44 @@ +/* + * 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 { act, render } from '@testing-library/react'; +import type { FormikContextType } from 'formik'; +import { Formik, FormikConsumer } from 'formik'; +import React from 'react'; + +import { FormChangesProvider } from './form_changes'; +import { FormLabel } from './form_label'; + +describe('FormLabel', () => { + it('should report form changes', () => { + const onSubmit = jest.fn(); + const report = jest.fn(); + + let formik: FormikContextType; + render( + + + + + {(value) => { + formik = value; + return null; + }} + + + + ); + + expect(report).toHaveBeenLastCalledWith(true); + + act(() => { + formik.setFieldValue('email', 'mail@example.com'); + }); + + expect(report).toHaveBeenLastCalledWith(false); + }); +}); diff --git a/x-pack/plugins/security/public/components/form_label.tsx b/x-pack/plugins/security/public/components/form_label.tsx new file mode 100644 index 0000000000000..724dfa9902b02 --- /dev/null +++ b/x-pack/plugins/security/public/components/form_label.tsx @@ -0,0 +1,58 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { useFormikContext } from 'formik'; +import type { FunctionComponent } from 'react'; +import React, { useEffect } from 'react'; + +import { useFormChangesContext } from './form_changes'; + +export interface FormLabelProps { + /** + * Name of target form field. + */ + for: string; +} + +/** + * Component that visually indicates whether a field value has changed. + * + * @example Renders a dot next to "Email" label when field value changes. + * ```typescript + * + * + * Email}> + * + * + * + * + * ``` + * + * @throws Error if not a child of a `` component. + * @throws Error if not a child of a `` component. + */ +export const FormLabel: FunctionComponent = (props) => { + const formik = useFormikContext(); + const { report } = useFormChangesContext(); + + const meta = formik.getFieldMeta(props.for); + const isEqual = meta.value === meta.initialValue; + + useEffect(() => report(isEqual), [isEqual]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + {props.children} + {!isEqual ? ( + + + + ) : undefined} + + ); +}; diff --git a/x-pack/plugins/security/public/components/form_row.test.tsx b/x-pack/plugins/security/public/components/form_row.test.tsx new file mode 100644 index 0000000000000..962bf5e79f974 --- /dev/null +++ b/x-pack/plugins/security/public/components/form_row.test.tsx @@ -0,0 +1,44 @@ +/* + * 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 { EuiFormRow } from '@elastic/eui'; +import { mount } from 'enzyme'; +import { Formik } from 'formik'; +import React from 'react'; + +import { FormRow } from './form_row'; + +describe('FormRow', () => { + it('should render form row with correct error states', () => { + const assertions = [ + { error: 'Error', touched: true, isInvalid: true }, + { error: 'Error', touched: false, isInvalid: false }, + { error: undefined, touched: true, isInvalid: false }, + ]; + assertions.forEach(({ error, touched, isInvalid }) => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find(EuiFormRow).props()).toEqual( + expect.objectContaining({ + error, + isInvalid, + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/security/public/components/form_row.tsx b/x-pack/plugins/security/public/components/form_row.tsx new file mode 100644 index 0000000000000..a6f4e475c3e19 --- /dev/null +++ b/x-pack/plugins/security/public/components/form_row.tsx @@ -0,0 +1,66 @@ +/* + * 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 { EuiFormRow, EuiText } from '@elastic/eui'; +import type { EuiFormRowProps } from '@elastic/eui'; +import { useFormikContext } from 'formik'; +import type { FunctionComponent } from 'react'; +import React, { Children } from 'react'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +export interface FormRowProps { + /** + * Optional name of form field. + * + * If not provided the name will be inferred from its child element. + */ + name?: string; +} + +/** + * Component that renders a form row with all error states for inline validation. + * + * @example + * ```typescript + * + * + * + * + * + * ``` + * + * @throws Error if not a child of a `` component. + * @throws Error if `name` prop is not set and can't be inferred from its child element. + */ +export const FormRow: FunctionComponent = (props) => { + const formik = useFormikContext(); + const child = Children.only(props.children); + const name = props.name ?? child.props.name; + + if (!name) { + throw new Error( + 'name prop is undefined, please verify you are either rendering itself or its child with a name prop.' + ); + } + + const meta = formik.getFieldMeta(name); + + return ( + + {child} + + ); +}; + +export const OptionalText: FunctionComponent = () => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/security/public/components/index.ts b/x-pack/plugins/security/public/components/index.ts new file mode 100644 index 0000000000000..4787bcacd8662 --- /dev/null +++ b/x-pack/plugins/security/public/components/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { SecurityApiClientsProvider, useSecurityApiClients } from './security_api_clients_provider'; +export type { SecurityApiClients } from './security_api_clients_provider'; +export { + AuthenticationProvider, + useAuthentication, + useUserProfile, + useCurrentUser, +} from './use_current_user'; +export { UserAvatar } from './user_avatar'; +export type { UserAvatarProps } from './user_avatar'; diff --git a/x-pack/plugins/security/public/components/security_api_clients_provider.ts b/x-pack/plugins/security/public/components/security_api_clients_provider.ts new file mode 100644 index 0000000000000..0434ea6b85390 --- /dev/null +++ b/x-pack/plugins/security/public/components/security_api_clients_provider.ts @@ -0,0 +1,30 @@ +/* + * 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 constate from 'constate'; + +import type { UserProfileAPIClient } from '../account_management'; +import type { UserAPIClient } from '../management'; + +/** + * Represents a collection of the high-level abstractions (clients) to interact with the Security specific APIs. + */ +export interface SecurityApiClients { + userProfiles: UserProfileAPIClient; + users: UserAPIClient; +} + +/** + * The `SecurityApiClientsProvider` React context provider is used to provide UI components with the Security API + * clients that can be subsequently consumed through `useSecurityApiClients` hook. + */ +export const [SecurityApiClientsProvider, useSecurityApiClients] = constate( + ({ userProfiles, users }: SecurityApiClients) => ({ + userProfiles, + users, + }) +); diff --git a/x-pack/plugins/security/public/components/use_current_user.ts b/x-pack/plugins/security/public/components/use_current_user.ts index 103952d7d34ef..12df907679384 100644 --- a/x-pack/plugins/security/public/components/use_current_user.ts +++ b/x-pack/plugins/security/public/components/use_current_user.ts @@ -7,7 +7,10 @@ import constate from 'constate'; import useAsync from 'react-use/lib/useAsync'; +import useObservable from 'react-use/lib/useObservable'; +import { useSecurityApiClients } from '.'; +import type { UserData } from '../../common'; import type { AuthenticationServiceSetup } from '../authentication'; export interface AuthenticationProviderProps { @@ -24,3 +27,9 @@ export function useCurrentUser() { const authc = useAuthentication(); return useAsync(authc.getCurrentUser, [authc]); } + +export function useUserProfile(dataPath?: string) { + const { userProfiles } = useSecurityApiClients(); + const dataUpdateState = useObservable(userProfiles.dataUpdates$); + return useAsync(() => userProfiles.get(dataPath), [userProfiles, dataUpdateState]); +} diff --git a/x-pack/plugins/security/public/components/user_avatar.tsx b/x-pack/plugins/security/public/components/user_avatar.tsx new file mode 100644 index 0000000000000..ff9bb1055bcc7 --- /dev/null +++ b/x-pack/plugins/security/public/components/user_avatar.tsx @@ -0,0 +1,50 @@ +/* + * 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 type { EuiAvatarProps } from '@elastic/eui'; +import { EuiAvatar, useEuiTheme } from '@elastic/eui'; +import type { FunctionComponent, HTMLAttributes } from 'react'; +import React from 'react'; + +import type { UserAvatarData, UserInfo } from '../../common'; +import { + getUserAvatarColor, + getUserAvatarInitials, + getUserDisplayName, + USER_AVATAR_MAX_INITIALS, +} from '../../common/model'; + +export interface UserAvatarProps extends Omit, 'color'> { + user?: Pick; + avatar?: UserAvatarData; + size?: EuiAvatarProps['size']; + isDisabled?: EuiAvatarProps['isDisabled']; +} + +export const UserAvatar: FunctionComponent = ({ user, avatar, ...rest }) => { + const { euiTheme } = useEuiTheme(); + + if (!user) { + return ; + } + + const displayName = getUserDisplayName(user); + + if (avatar?.imageUrl) { + return ; + } + + return ( + + ); +}; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index 158af4f2de8d2..3940361ad72bd 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -18,9 +18,8 @@ import { SecurityPlugin } from './plugin'; export type { SecurityPluginSetup, SecurityPluginStart }; export type { AuthenticatedUser } from '../common/model'; export type { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing'; +export type { UiApi, ChangePasswordProps, PersonalInfoProps } from './ui_api'; export type { UserMenuLink, SecurityNavControlServiceStart } from './nav_control'; -export type { UiApi } from './ui_api'; -export type { PersonalInfoProps, ChangePasswordProps } from './account_management'; export type { AuthenticationServiceStart, AuthenticationServiceSetup } from './authentication'; diff --git a/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx b/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx deleted file mode 100644 index 53c298e270112..0000000000000 --- a/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx +++ /dev/null @@ -1,297 +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 { - EuiCallOut, - EuiFieldPassword, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiIcon, - EuiLoadingContent, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import type { FunctionComponent } from 'react'; -import React from 'react'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; - -import { FormFlyout } from '../../../components/form_flyout'; -import { useCurrentUser } from '../../../components/use_current_user'; -import type { ValidationErrors } from '../../../components/use_form'; -import { useForm } from '../../../components/use_form'; -import { useInitialFocus } from '../../../components/use_initial_focus'; -import { UserAPIClient } from '../user_api_client'; - -export interface ChangePasswordFormValues { - current_password?: string; - password: string; - confirm_password: string; -} - -export interface ChangePasswordFlyoutProps { - username: string; - defaultValues?: ChangePasswordFormValues; - onCancel(): void; - onSuccess?(): void; -} - -export const validateChangePasswordForm = ( - values: ChangePasswordFormValues, - isCurrentUser: boolean -) => { - const errors: ValidationErrors = {}; - - if (isCurrentUser) { - if (!values.current_password) { - errors.current_password = i18n.translate( - 'xpack.security.management.users.changePasswordFlyout.currentPasswordRequiredError', - { - defaultMessage: 'Enter your current password.', - } - ); - } - } - - if (!values.password) { - errors.password = i18n.translate( - 'xpack.security.management.users.changePasswordFlyout.passwordRequiredError', - { - defaultMessage: 'Enter a new password.', - } - ); - } else if (values.password.length < 6) { - errors.password = i18n.translate( - 'xpack.security.management.users.changePasswordFlyout.passwordInvalidError', - { - defaultMessage: 'Password must be at least 6 characters.', - } - ); - } else if (values.password !== values.confirm_password) { - errors.confirm_password = i18n.translate( - 'xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError', - { - defaultMessage: 'Passwords do not match.', - } - ); - } - - return errors; -}; - -export const ChangePasswordFlyout: FunctionComponent = ({ - username, - defaultValues = { - current_password: '', - password: '', - confirm_password: '', - }, - onSuccess, - onCancel, -}) => { - const { services } = useKibana(); - const { value: currentUser, loading: isLoading } = useCurrentUser(); - const isCurrentUser = currentUser?.username === username; - const isSystemUser = username === 'kibana' || username === 'kibana_system'; - - const [form, eventHandlers] = useForm({ - onSubmit: async (values) => { - try { - await new UserAPIClient(services.http!).changePassword( - username, - values.password, - values.current_password - ); - services.notifications!.toasts.addSuccess( - i18n.translate('xpack.security.management.users.changePasswordFlyout.successMessage', { - defaultMessage: "Password changed for '{username}'.", - values: { username }, - }) - ); - onSuccess?.(); - } catch (error) { - if ((error as any).body?.message === 'security_exception') { - form.setError( - 'current_password', - i18n.translate( - 'xpack.security.management.users.changePasswordFlyout.currentPasswordInvalidError', - { - defaultMessage: 'Invalid password.', - } - ) - ); - } else { - services.notifications!.toasts.addDanger({ - title: i18n.translate( - 'xpack.security.management.users.changePasswordFlyout.errorMessage', - { - defaultMessage: 'Could not change password', - } - ), - text: (error as any).body?.message || error.message, - }); - throw error; - } - } - }, - validate: async (values) => validateChangePasswordForm(values, isCurrentUser), - defaultValues, - }); - - const firstFieldRef = useInitialFocus([isLoading]); - - return ( - - {isLoading ? ( - - ) : ( - - {isSystemUser ? ( - <> - -

- -

-

- -

-
- - - ) : undefined} - - - - - - - - - {username} - - - - - - {isCurrentUser ? ( - - - - ) : null} - - - - - - - - - {/* Hidden submit button is required for enter key to trigger form submission */} - -
- )} -
- ); -}; diff --git a/x-pack/plugins/security/public/management/users/edit_user/change_password_modal.tsx b/x-pack/plugins/security/public/management/users/edit_user/change_password_modal.tsx new file mode 100644 index 0000000000000..beba64e6b2fb9 --- /dev/null +++ b/x-pack/plugins/security/public/management/users/edit_user/change_password_modal.tsx @@ -0,0 +1,304 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFieldPassword, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiIcon, + EuiLoadingContent, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSpacer, + EuiText, + useGeneratedHtmlId, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { euiThemeVars } from '@kbn/ui-theme'; + +import { useCurrentUser } from '../../../components/use_current_user'; +import type { ValidationErrors } from '../../../components/use_form'; +import { useForm } from '../../../components/use_form'; +import { useInitialFocus } from '../../../components/use_initial_focus'; +import { UserAPIClient } from '../user_api_client'; + +export interface ChangePasswordFormValues { + current_password?: string; + password: string; + confirm_password: string; +} + +export interface ChangePasswordModalProps { + username: string; + defaultValues?: ChangePasswordFormValues; + onCancel(): void; + onSuccess?(): void; +} + +export const validateChangePasswordForm = ( + values: ChangePasswordFormValues, + isCurrentUser: boolean +) => { + const errors: ValidationErrors = {}; + + if (isCurrentUser) { + if (!values.current_password) { + errors.current_password = i18n.translate( + 'xpack.security.management.users.changePasswordForm.currentPasswordRequiredError', + { defaultMessage: 'Enter your current password.' } + ); + } + } + + if (!values.password) { + errors.password = i18n.translate( + 'xpack.security.management.users.changePasswordForm.passwordRequiredError', + { defaultMessage: 'Enter a new password.' } + ); + } else if (values.password.length < 6) { + errors.password = i18n.translate( + 'xpack.security.management.users.changePasswordForm.passwordInvalidError', + { defaultMessage: 'Enter at least 6 characters.' } + ); + } else if (values.password !== values.confirm_password) { + errors.confirm_password = i18n.translate( + 'xpack.security.management.users.changePasswordForm.confirmPasswordInvalidError', + { defaultMessage: 'Passwords do not match.' } + ); + } + + return errors; +}; + +export const ChangePasswordModal: FunctionComponent = ({ + username, + defaultValues = { + current_password: '', + password: '', + confirm_password: '', + }, + onSuccess, + onCancel, +}) => { + const { services } = useKibana(); + const { value: currentUser, loading: isLoading } = useCurrentUser(); + const isCurrentUser = currentUser?.username === username; + const isSystemUser = username === 'kibana' || username === 'kibana_system'; + + const [form, eventHandlers] = useForm({ + onSubmit: async (values) => { + try { + await new UserAPIClient(services.http!).changePassword( + username, + values.password, + values.current_password + ); + services.notifications!.toasts.addSuccess( + i18n.translate('xpack.security.management.users.changePasswordForm.successMessage', { + defaultMessage: 'Password successfully changed', + }) + ); + onSuccess?.(); + } catch (error) { + if ((error as any).body?.statusCode === 403) { + form.setError( + 'current_password', + i18n.translate( + 'xpack.security.management.users.changePasswordForm.currentPasswordInvalidError', + { defaultMessage: 'Invalid password.' } + ) + ); + } else { + services.notifications!.toasts.addDanger({ + title: i18n.translate( + 'xpack.security.management.users.changePasswordForm.errorMessage', + { defaultMessage: 'Could not change password' } + ), + text: (error as any).body?.message || error.message, + }); + throw error; + } + } + }, + validate: async (values) => validateChangePasswordForm(values, isCurrentUser), + defaultValues, + }); + + const firstFieldRef = useInitialFocus([isLoading]); + const modalFormId = useGeneratedHtmlId({ prefix: 'modalForm' }); + + return ( + + + + + + + + {isLoading ? ( + + ) : ( + + {isSystemUser ? ( + <> + +

+ +

+

+ +

+
+ + + ) : undefined} + + {isCurrentUser ? ( + <> + + + + + ) : ( + + + + + + + + {username} + + + + + )} + + + + + + + +
+ )} +
+ + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/change_password_model.test.tsx similarity index 90% rename from x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.test.tsx rename to x-pack/plugins/security/public/management/users/edit_user/change_password_model.test.tsx index b0b8ca2030fa0..f040803ace705 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/change_password_model.test.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import type { ChangePasswordFormValues } from './change_password_flyout'; -import { validateChangePasswordForm } from './change_password_flyout'; +import type { ChangePasswordFormValues } from './change_password_modal'; +import { validateChangePasswordForm } from './change_password_modal'; -describe('ChangePasswordFlyout', () => { +describe('ChangePasswordModal', () => { describe('#validateChangePasswordForm', () => { describe('for current user', () => { it('should show an error when it is current user with no current password', () => { @@ -41,11 +41,11 @@ describe('ChangePasswordFlyout', () => { it('should show errors when the new password is not at least 6 characters', () => { expect(validateChangePasswordForm({ password: '12345', confirm_password: '12345' }, true)) .toMatchInlineSnapshot(` - Object { - "current_password": "Enter your current password.", - "password": "Password must be at least 6 characters.", - } - `); + Object { + "current_password": "Enter your current password.", + "password": "Enter at least 6 characters.", + } + `); }); it('should show errors when new password does not match confirmation password', () => { @@ -100,7 +100,7 @@ describe('ChangePasswordFlyout', () => { expect(validateChangePasswordForm({ password: '1234', confirm_password: '1234' }, false)) .toMatchInlineSnapshot(` Object { - "password": "Password must be at least 6 characters.", + "password": "Enter at least 6 characters.", } `); }); diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx index a64ca3aca99eb..231ef70fed5bf 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx @@ -32,7 +32,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { getUserDisplayName } from '../../../../common/model'; import { UserAPIClient } from '../user_api_client'; import { isUserDeprecated, isUserReserved } from '../user_utils'; -import { ChangePasswordFlyout } from './change_password_flyout'; +import { ChangePasswordModal } from './change_password_modal'; import { ConfirmDeleteUsers } from './confirm_delete_users'; import { ConfirmDisableUsers } from './confirm_disable_users'; import { ConfirmEnableUsers } from './confirm_enable_users'; @@ -157,7 +157,7 @@ export const EditUserPage: FunctionComponent = ({ username }) /> {action === 'changePassword' ? ( - setAction('none')} onSuccess={() => setAction('none')} diff --git a/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx b/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx index d1134d0959a16..83cf8dae89416 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx @@ -255,7 +255,7 @@ export const UserForm: FunctionComponent = ({ !isNewUser && !isReservedUser ? i18n.translate( 'xpack.security.management.users.userForm.changingUserNameAfterCreationDescription', - { defaultMessage: `Username can't be changed once created.` } + { defaultMessage: 'User name cannot be changed after account creation.' } ) : undefined } diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.scss b/x-pack/plugins/security/public/nav_control/nav_control_component.scss deleted file mode 100644 index a3e04b08cfac2..0000000000000 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.chrNavControl__userMenu { - .euiContextMenuPanelTitle { - // Uppercased by default, override to match actual username - text-transform: none; - } - - .euiContextMenuItem { - // Temp fix for EUI issue https://github.com/elastic/eui/issues/3092 - line-height: normal; - } -} \ No newline at end of file diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index be74cca3d2671..20f293f37fc26 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -5,28 +5,57 @@ * 2.0. */ -import { EuiContextMenuItem, EuiHeaderSectionItemButton, EuiPopover } from '@elastic/eui'; +import { EuiContextMenu } from '@elastic/eui'; +import { shallow } from 'enzyme'; +import type { ReactElement } from 'react'; import React from 'react'; +import { act } from 'react-dom/test-utils'; +import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject } from 'rxjs'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; - -import type { AuthenticatedUser } from '../../common/model'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; +import { userProfileMock } from '../../common/model/user_profile.mock'; +import * as UseCurrentUserImports from '../components/use_current_user'; import { SecurityNavControl } from './nav_control_component'; +jest.mock('../components/use_current_user'); +jest.mock('react-use/lib/useObservable'); + +const useObservableMock = useObservable as jest.Mock; +const useUserProfileMock = jest.spyOn(UseCurrentUserImports, 'useUserProfile'); +const useCurrentUserMock = jest.spyOn(UseCurrentUserImports, 'useCurrentUser'); + +const userProfile = userProfileMock.create(); +const userMenuLinks$ = new BehaviorSubject([]); + describe('SecurityNavControl', () => { - it(`renders a loading spinner when the user promise hasn't resolved yet.`, async () => { - const props = { - user: new Promise(() => mockAuthenticatedUser()), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), - }; - - const wrapper = shallowWithIntl(); - const { button } = wrapper.find(EuiPopover).props(); - expect(button).toMatchInlineSnapshot(` + beforeEach(() => { + useUserProfileMock.mockReset(); + useUserProfileMock.mockReturnValue({ + loading: false, + value: userProfile, + }); + + useCurrentUserMock.mockReset(); + useCurrentUserMock.mockReturnValue({ + loading: false, + value: mockAuthenticatedUser(), + }); + + useObservableMock.mockReset(); + useObservableMock.mockImplementation( + (observable: BehaviorSubject, initialValue = {}) => observable.value ?? initialValue + ); + }); + + it('should render an avatar when user profile has loaded', async () => { + const wrapper = shallow( + + ); + + expect(useUserProfileMock).toHaveBeenCalledTimes(1); + expect(useCurrentUserMock).toHaveBeenCalledTimes(1); + expect(wrapper.prop('button')).toMatchInlineSnapshot(` { aria-label="Account menu" data-test-subj="userMenuButton" onClick={[Function]} + style={ + Object { + "lineHeight": "normal", + } + } > - `); }); - it(`renders an avatar after the user promise resolves.`, async () => { - const props = { - user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), - }; - - const wrapper = shallowWithIntl(); - await nextTick(); - wrapper.update(); - const { button } = wrapper.find(EuiPopover).props(); - expect(button).toMatchInlineSnapshot(` + it('should render a spinner while loading', () => { + useUserProfileMock.mockReturnValue({ + loading: true, + }); + useCurrentUserMock.mockReturnValue({ + loading: true, + }); + + const wrapper = shallow( + + ); + + expect(useUserProfileMock).toHaveBeenCalledTimes(1); + expect(useCurrentUserMock).toHaveBeenCalledTimes(1); + expect(wrapper.prop('button')).toMatchInlineSnapshot(` { aria-label="Account menu" data-test-subj="userMenuButton" onClick={[Function]} + style={ + Object { + "lineHeight": "normal", + } + } > - `); }); - it(`doesn't render the popover when the user hasn't been loaded yet`, async () => { - const props = { - user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), - }; + it('should open popover when avatar is clicked', async () => { + const wrapper = shallow( + + ); - const wrapper = mountWithIntl(); - // not awaiting the user promise + act(() => { + wrapper.prop('button').props.onClick(); + wrapper.update(); + }); - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); - - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); + expect(wrapper.prop('isOpen')).toEqual(true); }); - it('renders a popover when the avatar is clicked.', async () => { - const props = { - user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([]), - }; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); + it('should not open popover while loading', () => { + useUserProfileMock.mockReturnValue({ + loading: true, + }); + useCurrentUserMock.mockReturnValue({ + loading: true, + }); - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); + const wrapper = shallow( + + ); - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + act(() => { + wrapper.prop('button').props.onClick(); + wrapper.update(); + }); - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(1); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); + expect(wrapper.prop('isOpen')).toEqual(false); }); - it('renders a popover with additional user menu links registered by other plugins', async () => { - const props = { - user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([ - { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, - { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, - { label: 'link3', href: 'path-to-link-3', iconType: 'empty', order: 3 }, - ]), - }; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(0); - expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(0); - expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); - - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(1); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(1); - expect(findTestSubject(wrapper, 'userMenuLink__link1')).toHaveLength(1); - expect(findTestSubject(wrapper, 'userMenuLink__link2')).toHaveLength(1); - expect(findTestSubject(wrapper, 'userMenuLink__link3')).toHaveLength(1); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); - }); - - it('properly renders a popover for anonymous user.', async () => { - const props = { - user: Promise.resolve( - mockAuthenticatedUser({ - authentication_provider: { type: 'anonymous', name: 'does no matter' }, - }) - ), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([ - { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, - { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, - { label: 'link3', href: 'path-to-link-3', iconType: 'empty', order: 3 }, - ]), - }; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(0); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(0); - - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); - - expect(findTestSubject(wrapper, 'userMenu')).toHaveLength(1); - expect(findTestSubject(wrapper, 'profileLink')).toHaveLength(0); - expect(findTestSubject(wrapper, 'logoutLink')).toHaveLength(1); - - expect(findTestSubject(wrapper, 'logoutLink').text()).toBe('Log in'); + it('should render additional user menu links registered by other plugins', async () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(EuiContextMenu).prop('panels')).toMatchInlineSnapshot(` + Array [ + Object { + "id": 0, + "items": Array [ + Object { + "data-test-subj": "profileLink", + "href": "", + "icon": , + "name": , + }, + Object { + "data-test-subj": "userMenuLink__link1", + "href": "path-to-link-1", + "icon": , + "name": "link1", + }, + Object { + "data-test-subj": "userMenuLink__link2", + "href": "path-to-link-2", + "icon": , + "name": "link2", + }, + Object { + "data-test-subj": "userMenuLink__link3", + "href": "path-to-link-3", + "icon": , + "name": "link3", + }, + Object { + "data-test-subj": "logoutLink", + "href": "", + "icon": , + "name": , + }, + ], + "title": "full name", + }, + ] + `); }); - it('properly renders without a custom profile link.', async () => { - const props = { - user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([ - { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, - { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, - ]), - }; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([]); - - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); - - expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([ - 'Profile', - 'link1', - 'link2', - 'Log out', - ]); + it('should render custom profile link registered by other plugins', async () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(EuiContextMenu).prop('panels')).toMatchInlineSnapshot(` + Array [ + Object { + "id": 0, + "items": Array [ + Object { + "data-test-subj": "userMenuLink__link1", + "href": "path-to-link-1", + "icon": , + "name": "link1", + }, + Object { + "data-test-subj": "userMenuLink__link2", + "href": "path-to-link-2", + "icon": , + "name": "link2", + }, + Object { + "data-test-subj": "userMenuLink__link3", + "href": "path-to-link-3", + "icon": , + "name": "link3", + }, + Object { + "data-test-subj": "profileLink", + "href": "", + "icon": , + "name": , + }, + Object { + "data-test-subj": "logoutLink", + "href": "", + "icon": , + "name": , + }, + ], + "title": "full name", + }, + ] + `); }); - it('properly renders with a custom profile link.', async () => { - const props = { - user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), - editProfileUrl: '', - logoutUrl: '', - userMenuLinks$: new BehaviorSubject([ - { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, - { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2, setAsProfile: true }, - ]), - }; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([]); - - wrapper.find(EuiHeaderSectionItemButton).simulate('click'); - - expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([ - 'link1', - 'link2', - 'Preferences', - 'Log out', - ]); + it('should render anonymous user', async () => { + useUserProfileMock.mockReturnValue({ + loading: false, + value: undefined, + error: new Error('404'), + }); + + useCurrentUserMock.mockReturnValue({ + loading: false, + value: mockAuthenticatedUser({ + authentication_provider: { type: 'anonymous', name: 'does no matter' }, + }), + }); + + const wrapper = shallow( + + ); + + expect(wrapper.find(EuiContextMenu).prop('panels')).toMatchInlineSnapshot(` + Array [ + Object { + "id": 0, + "items": Array [ + Object { + "data-test-subj": "logoutLink", + "href": "", + "icon": , + "name": , + }, + ], + "title": "full name", + }, + ] + `); }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 541d0c161a87e..1a32a083793f3 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -5,24 +5,25 @@ * 2.0. */ -import './nav_control_component.scss'; - import type { EuiContextMenuPanelItemDescriptor, IconType } from '@elastic/eui'; import { - EuiAvatar, EuiContextMenu, EuiHeaderSectionItemButton, EuiIcon, EuiLoadingSpinner, EuiPopover, } from '@elastic/eui'; -import React, { Component } from 'react'; -import type { Observable, Subscription } from 'rxjs'; +import type { FunctionComponent } from 'react'; +import React, { useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import type { Observable } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { AuthenticatedUser } from '../../common/model'; +import type { UserAvatarData } from '../../common'; +import { getUserDisplayName, isUserAnonymous } from '../../common/model'; +import { useCurrentUser, UserAvatar, useUserProfile } from '../components'; export interface UserMenuLink { label: string; @@ -32,174 +33,131 @@ export interface UserMenuLink { setAsProfile?: boolean; } -interface Props { - user: Promise; +interface SecurityNavControlProps { editProfileUrl: string; logoutUrl: string; userMenuLinks$: Observable; } -interface State { - isOpen: boolean; - authenticatedUser: AuthenticatedUser | null; - userMenuLinks: UserMenuLink[]; -} - -export class SecurityNavControl extends Component { - private subscription?: Subscription; - - constructor(props: Props) { - super(props); - - this.state = { - isOpen: false, - authenticatedUser: null, - userMenuLinks: [], - }; - - props.user.then((authenticatedUser) => { - this.setState({ - authenticatedUser, - }); - }); - } - - componentDidMount() { - this.subscription = this.props.userMenuLinks$.subscribe(async (userMenuLinks) => { - this.setState({ userMenuLinks }); - }); - } - - componentWillUnmount() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - - onMenuButtonClick = () => { - if (!this.state.authenticatedUser) { - return; - } - - this.setState({ - isOpen: !this.state.isOpen, - }); - }; - - closeMenu = () => { - this.setState({ - isOpen: false, - }); - }; - - render() { - const { editProfileUrl, logoutUrl } = this.props; - const { authenticatedUser, userMenuLinks } = this.state; - - const username = - (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; - - const buttonContents = authenticatedUser ? ( - - ) : ( - - ); - - const button = ( - - {buttonContents} - - ); - - const isAnonymousUser = authenticatedUser?.authentication_provider.type === 'anonymous'; - const items: EuiContextMenuPanelItemDescriptor[] = []; - - if (userMenuLinks.length) { - const userMenuLinkMenuItems = userMenuLinks - .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) - .map(({ label, iconType, href }: UserMenuLink) => ({ - name: label, - icon: , - href, - 'data-test-subj': `userMenuLink__${label}`, - })); - items.push(...userMenuLinkMenuItems); - } - - if (!isAnonymousUser) { - const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true); - const profileMenuItem = { - name: ( - - ), - icon: , - href: editProfileUrl, - 'data-test-subj': 'profileLink', - }; - - // Set this as the first link if there is no user-defined profile link - if (!hasCustomProfileLinks) { - items.unshift(profileMenuItem); - } else { - items.push(profileMenuItem); - } - } - - const logoutMenuItem = { - name: isAnonymousUser ? ( - = ({ + editProfileUrl, + logoutUrl, + userMenuLinks$, +}) => { + const userMenuLinks = useObservable(userMenuLinks$, []); + const [isOpen, setIsOpen] = useState(false); + + const userProfile = useUserProfile<{ avatar: UserAvatarData }>('avatar'); + const currentUser = useCurrentUser(); // User profiles do not exist for anonymous users so need to fetch current user as well + + const displayName = currentUser.value ? getUserDisplayName(currentUser.value) : ''; + + const button = ( + setIsOpen((value) => (currentUser.value ? !value : false))} + data-test-subj="userMenuButton" + style={{ lineHeight: 'normal' }} + > + {currentUser.value && userProfile.value ? ( + + ) : currentUser.value && userProfile.error ? ( + ) : ( + + )} + + ); + + const isAnonymous = currentUser.value ? isUserAnonymous(currentUser.value) : false; + const items: EuiContextMenuPanelItemDescriptor[] = []; + if (userMenuLinks.length) { + const userMenuLinkMenuItems = userMenuLinks + .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) + .map(({ label, iconType, href }: UserMenuLink) => ({ + name: label, + icon: , + href, + 'data-test-subj': `userMenuLink__${label}`, + })); + items.push(...userMenuLinkMenuItems); + } + + if (!isAnonymous) { + const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true); + const profileMenuItem: EuiContextMenuPanelItemDescriptor = { + name: ( ), - icon: , - href: logoutUrl, - 'data-test-subj': 'logoutLink', + icon: , + href: editProfileUrl, + 'data-test-subj': 'profileLink', }; - items.push(logoutMenuItem); - const panels = [ - { - id: 0, - title: username, - items, - }, - ]; - - return ( - -
- -
-
- ); + // Set this as the first link if there is no user-defined profile link + if (!hasCustomProfileLinks) { + items.unshift(profileMenuItem); + } else { + items.push(profileMenuItem); + } } -} + + items.push({ + name: isAnonymous ? ( + + ) : ( + + ), + icon: , + href: logoutUrl, + 'data-test-subj': 'logoutLink', + }); + + return ( + setIsOpen(false)} + panelPaddingSize="none" + buffer={0} + > +
+ +
+
+ ); +}; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index d23ce4ac0ed2e..180446a2e38d2 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -7,15 +7,29 @@ import { BehaviorSubject } from 'rxjs'; +import type { httpServiceMock } from '@kbn/core/public/mocks'; import { coreMock } from '@kbn/core/public/mocks'; import type { ILicense } from '@kbn/licensing-plugin/public'; import { nextTick } from '@kbn/test-jest-helpers'; import { SecurityLicenseService } from '../../common/licensing'; -import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; -import { securityMock } from '../mocks'; +import { UserProfileAPIClient } from '../account_management'; +import { authenticationMock } from '../authentication/index.mock'; +import * as UseCurrentUserImports from '../components/use_current_user'; +import { UserAPIClient } from '../management'; import { SecurityNavControlService } from './nav_control_service'; +const useUserProfileMock = jest.spyOn(UseCurrentUserImports, 'useUserProfile'); +const useCurrentUserMock = jest.spyOn(UseCurrentUserImports, 'useCurrentUser'); + +useUserProfileMock.mockReturnValue({ + loading: true, +}); + +useCurrentUserMock.mockReturnValue({ + loading: true, +}); + const validLicense = { isAvailable: true, getFeature: (feature) => { @@ -29,25 +43,28 @@ const validLicense = { hasAtLeast: (...candidates) => true, } as ILicense; +const authc = authenticationMock.createStart(); + +const mockApiClients = (http: ReturnType) => ({ + userProfiles: new UserProfileAPIClient(http), + users: new UserAPIClient(http), +}); + describe('SecurityNavControlService', () => { it('can render and cleanup the control via the mount() function', async () => { const license$ = new BehaviorSubject(validLicense); + const coreStart = coreMock.createStart(); const navControlService = new SecurityNavControlService(); - const mockSecuritySetup = securityMock.createSetup(); - mockSecuritySetup.authc.getCurrentUser.mockResolvedValue( - mockAuthenticatedUser({ username: 'some-user', full_name: undefined }) - ); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: mockSecuritySetup.authc, logoutUrl: '/some/logout/url', + securityApiClients: mockApiClients(coreStart.http), }); - const coreStart = coreMock.createStart(); coreStart.chrome.navControls.registerRight = jest.fn(); - navControlService.start({ core: coreStart }); + navControlService.start({ core: coreStart, authc }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1); const [{ mount }] = coreStart.chrome.navControls.registerRight.mock.calls[0]; @@ -72,6 +89,7 @@ describe('SecurityNavControlService', () => { aria-label="Account menu" class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" data-test-subj="userMenuButton" + style="line-height: normal;" type="button" > { - + @@ -113,16 +122,16 @@ describe('SecurityNavControlService', () => { it('should register the nav control once the license supports it', () => { const license$ = new BehaviorSubject({} as ILicense); + const coreStart = coreMock.createStart(); const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', + securityApiClients: mockApiClients(coreStart.http), }); - const coreStart = coreMock.createStart(); - navControlService.start({ core: coreStart }); + navControlService.start({ core: coreStart, authc }); expect(coreStart.chrome.navControls.registerRight).not.toHaveBeenCalled(); @@ -133,33 +142,33 @@ describe('SecurityNavControlService', () => { it('should not register the nav control for anonymous paths', () => { const license$ = new BehaviorSubject(validLicense); + const coreStart = coreMock.createStart(); const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', + securityApiClients: mockApiClients(coreStart.http), }); - const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - navControlService.start({ core: coreStart }); + navControlService.start({ core: coreStart, authc }); expect(coreStart.chrome.navControls.registerRight).not.toHaveBeenCalled(); }); it('should only register the nav control once', () => { const license$ = new BehaviorSubject(validLicense); + const coreStart = coreMock.createStart(); const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', + securityApiClients: mockApiClients(coreStart.http), }); - const coreStart = coreMock.createStart(); - navControlService.start({ core: coreStart }); + navControlService.start({ core: coreStart, authc }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1); @@ -172,48 +181,52 @@ describe('SecurityNavControlService', () => { it('should allow for re-registration if the service is restarted', () => { const license$ = new BehaviorSubject(validLicense); + const coreStart = coreMock.createStart(); const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', + securityApiClients: mockApiClients(coreStart.http), }); - const coreStart = coreMock.createStart(); - navControlService.start({ core: coreStart }); + navControlService.start({ core: coreStart, authc }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1); navControlService.stop(); - navControlService.start({ core: coreStart }); + navControlService.start({ core: coreStart, authc }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(2); }); describe(`#start`, () => { let navControlService: SecurityNavControlService; beforeEach(() => { + const coreSetup = coreMock.createSetup(); const license$ = new BehaviorSubject({} as ILicense); navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', + securityApiClients: mockApiClients(coreSetup.http), }); }); it('should return functions to register and retrieve user menu links', () => { const coreStart = coreMock.createStart(); - const navControlServiceStart = navControlService.start({ core: coreStart }); + const navControlServiceStart = navControlService.start({ core: coreStart, authc }); expect(navControlServiceStart).toHaveProperty('getUserMenuLinks$'); expect(navControlServiceStart).toHaveProperty('addUserMenuLinks'); }); it('should register custom user menu links to be displayed in the nav controls', (done) => { const coreStart = coreMock.createStart(); - const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ + core: coreStart, + authc, + }); const userMenuLinks$ = getUserMenuLinks$(); addUserMenuLinks([ @@ -240,7 +253,10 @@ describe('SecurityNavControlService', () => { it('should retrieve user menu links sorted by order', (done) => { const coreStart = coreMock.createStart(); - const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ + core: coreStart, + authc, + }); const userMenuLinks$ = getUserMenuLinks$(); addUserMenuLinks([ @@ -307,7 +323,10 @@ describe('SecurityNavControlService', () => { it('should allow adding a custom profile link', () => { const coreStart = coreMock.createStart(); - const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ + core: coreStart, + authc, + }); const userMenuLinks$ = getUserMenuLinks$(); addUserMenuLinks([ @@ -327,7 +346,10 @@ describe('SecurityNavControlService', () => { it('should not allow adding more than one custom profile link', () => { const coreStart = coreMock.createStart(); - const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ + core: coreStart, + authc, + }); const userMenuLinks$ = getUserMenuLinks$(); expect(() => { diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 63e753f8646cd..592f5d16f523e 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -6,28 +6,33 @@ */ import { sortBy } from 'lodash'; +import type { FunctionComponent } from 'react'; import React from 'react'; import ReactDOM from 'react-dom'; import type { Observable, Subscription } from 'rxjs'; import { BehaviorSubject, ReplaySubject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; -import type { CoreStart } from '@kbn/core/public'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import type { CoreStart, CoreTheme } from '@kbn/core/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import type { SecurityLicense } from '../../common/licensing'; import type { AuthenticationServiceSetup } from '../authentication'; +import type { SecurityApiClients } from '../components'; +import { AuthenticationProvider, SecurityApiClientsProvider } from '../components'; import type { UserMenuLink } from './nav_control_component'; import { SecurityNavControl } from './nav_control_component'; interface SetupDeps { securityLicense: SecurityLicense; - authc: AuthenticationServiceSetup; logoutUrl: string; + securityApiClients: SecurityApiClients; } interface StartDeps { core: CoreStart; + authc: AuthenticationServiceSetup; } export interface SecurityNavControlServiceStart { @@ -44,8 +49,8 @@ export interface SecurityNavControlServiceStart { export class SecurityNavControlService { private securityLicense!: SecurityLicense; - private authc!: AuthenticationServiceSetup; private logoutUrl!: string; + private securityApiClients!: SecurityApiClients; private navControlRegistered!: boolean; @@ -54,13 +59,13 @@ export class SecurityNavControlService { private readonly stop$ = new ReplaySubject(1); private userMenuLinks$ = new BehaviorSubject([]); - public setup({ securityLicense, authc, logoutUrl }: SetupDeps) { + public setup({ securityLicense, logoutUrl, securityApiClients }: SetupDeps) { this.securityLicense = securityLicense; - this.authc = authc; this.logoutUrl = logoutUrl; + this.securityApiClients = securityApiClients; } - public start({ core }: StartDeps): SecurityNavControlServiceStart { + public start({ core, authc }: StartDeps): SecurityNavControlServiceStart { this.securityFeaturesSubscription = this.securityLicense.features$.subscribe( ({ showLinks }) => { const isAnonymousPath = core.http.anonymousPaths.isAnonymous(window.location.pathname); @@ -68,7 +73,7 @@ export class SecurityNavControlService { const shouldRegisterNavControl = !isAnonymousPath && showLinks && !this.navControlRegistered; if (shouldRegisterNavControl) { - this.registerSecurityNavControl(core); + this.registerSecurityNavControl(core, authc); } } ); @@ -110,32 +115,28 @@ export class SecurityNavControlService { this.stop$.next(); } - private registerSecurityNavControl( - core: Pick - ) { + private registerSecurityNavControl(core: CoreStart, authc: AuthenticationServiceSetup) { const { theme$ } = core.theme; - const currentUserPromise = this.authc.getCurrentUser(); core.chrome.navControls.registerRight({ order: 2000, - mount: (el: HTMLElement) => { - const I18nContext = core.i18n.Context; - - const props = { - user: currentUserPromise, - editProfileUrl: core.http.basePath.prepend('/security/account'), - logoutUrl: this.logoutUrl, - userMenuLinks$: this.userMenuLinks$, - }; + mount: (element: HTMLElement) => { ReactDOM.render( - - - - - , - el + + + , + element ); - return () => ReactDOM.unmountComponentAtNode(el); + return () => ReactDOM.unmountComponentAtNode(element); }, }); @@ -146,3 +147,28 @@ export class SecurityNavControlService { return sortBy(userMenuLinks, 'order'); } } + +export interface ProvidersProps { + authc: AuthenticationServiceSetup; + services: CoreStart; + securityApiClients: SecurityApiClients; + theme$: Observable; +} + +export const Providers: FunctionComponent = ({ + authc, + services, + theme$, + securityApiClients, + children, +}) => ( + + + + + {children} + + + + +); diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 7ca2e2f353efa..f648c4963884e 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -23,12 +23,12 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { SecurityLicenseService } from '../common/licensing'; import type { SecurityLicense } from '../common/licensing'; -import { accountManagementApp } from './account_management'; +import { accountManagementApp, UserProfileAPIClient } from './account_management'; import { AnonymousAccessService } from './anonymous_access'; import type { AuthenticationServiceSetup, AuthenticationServiceStart } from './authentication'; import { AuthenticationService } from './authentication'; import type { ConfigType } from './config'; -import { ManagementService } from './management'; +import { ManagementService, UserAPIClient } from './management'; import type { SecurityNavControlServiceStart } from './nav_control'; import { SecurityNavControlService } from './nav_control'; import { SecurityCheckupService } from './security_checkup'; @@ -91,16 +91,22 @@ export class SecurityPlugin http: core.http, }); + const securityApiClients = { + userProfiles: new UserProfileAPIClient(core.http), + users: new UserAPIClient(core.http), + }; + this.navControlService.setup({ securityLicense: license, - authc: this.authc, logoutUrl: getLogoutUrl(core.http), + securityApiClients, }); accountManagementApp.create({ authc: this.authc, application: core.application, getStartServices: core.getStartServices, + securityApiClients, }); if (management) { @@ -167,7 +173,7 @@ export class SecurityPlugin return { uiApi: getUiApi({ core }), - navControlService: this.navControlService.start({ core }), + navControlService: this.navControlService.start({ core, authc: this.authc }), authc: this.authc as AuthenticationServiceStart, }; } @@ -206,6 +212,7 @@ export interface SecurityPluginStart { authc: AuthenticationServiceStart; /** * Exposes UI components that will be loaded asynchronously. + * @deprecated */ uiApi: UiApi; } diff --git a/x-pack/plugins/security/public/account_management/change_password/change_password.tsx b/x-pack/plugins/security/public/ui_api/change_password/change_password.tsx similarity index 100% rename from x-pack/plugins/security/public/account_management/change_password/change_password.tsx rename to x-pack/plugins/security/public/ui_api/change_password/change_password.tsx diff --git a/x-pack/plugins/security/public/account_management/change_password/change_password_async.tsx b/x-pack/plugins/security/public/ui_api/change_password/change_password_async.tsx similarity index 100% rename from x-pack/plugins/security/public/account_management/change_password/change_password_async.tsx rename to x-pack/plugins/security/public/ui_api/change_password/change_password_async.tsx diff --git a/x-pack/plugins/security/public/account_management/change_password/index.ts b/x-pack/plugins/security/public/ui_api/change_password/index.ts similarity index 100% rename from x-pack/plugins/security/public/account_management/change_password/index.ts rename to x-pack/plugins/security/public/ui_api/change_password/index.ts diff --git a/x-pack/plugins/security/public/ui_api/components.tsx b/x-pack/plugins/security/public/ui_api/components.tsx index 5c899ab8c5ab5..7ab626bad1571 100644 --- a/x-pack/plugins/security/public/ui_api/components.tsx +++ b/x-pack/plugins/security/public/ui_api/components.tsx @@ -18,9 +18,9 @@ import type { CoreStart } from '@kbn/core/public'; * It happens because the bundle starts to also include all the sync dependencies * available through the index file. */ -import { getChangePasswordComponent } from '../account_management/change_password/change_password_async'; -import { getPersonalInfoComponent } from '../account_management/personal_info/personal_info_async'; +import { getChangePasswordComponent } from './change_password/change_password_async'; import { LazyWrapper } from './lazy_wrapper'; +import { getPersonalInfoComponent } from './personal_info/personal_info_async'; export interface GetComponentsOptions { core: CoreStart; diff --git a/x-pack/plugins/security/public/ui_api/index.ts b/x-pack/plugins/security/public/ui_api/index.ts index ba2e1bfb5f36b..7029a0547c70a 100644 --- a/x-pack/plugins/security/public/ui_api/index.ts +++ b/x-pack/plugins/security/public/ui_api/index.ts @@ -9,8 +9,11 @@ import type { ReactElement } from 'react'; import type { CoreStart } from '@kbn/core/public'; -import type { ChangePasswordProps, PersonalInfoProps } from '../account_management'; +import type { ChangePasswordProps } from './change_password'; import { getComponents } from './components'; +import type { PersonalInfoProps } from './personal_info'; + +export type { ChangePasswordProps, PersonalInfoProps }; interface GetUiApiOptions { core: CoreStart; diff --git a/x-pack/plugins/security/public/account_management/personal_info/index.ts b/x-pack/plugins/security/public/ui_api/personal_info/index.ts similarity index 100% rename from x-pack/plugins/security/public/account_management/personal_info/index.ts rename to x-pack/plugins/security/public/ui_api/personal_info/index.ts diff --git a/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx b/x-pack/plugins/security/public/ui_api/personal_info/personal_info.tsx similarity index 100% rename from x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx rename to x-pack/plugins/security/public/ui_api/personal_info/personal_info.tsx diff --git a/x-pack/plugins/security/public/account_management/personal_info/personal_info_async.tsx b/x-pack/plugins/security/public/ui_api/personal_info/personal_info_async.tsx similarity index 100% rename from x-pack/plugins/security/public/account_management/personal_info/personal_info_async.tsx rename to x-pack/plugins/security/public/ui_api/personal_info/personal_info_async.tsx diff --git a/x-pack/plugins/security/server/authentication/authentication_result.test.ts b/x-pack/plugins/security/server/authentication/authentication_result.test.ts index e890f99e0da6e..73465a9fdaf1e 100644 --- a/x-pack/plugins/security/server/authentication/authentication_result.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_result.test.ts @@ -8,6 +8,7 @@ import Boom from '@hapi/boom'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; +import type { UserProfileGrant } from '../user_profile'; import { AuthenticationResult } from './authentication_result'; describe('AuthenticationResult', () => { @@ -21,6 +22,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.redirected()).toBe(false); expect(authenticationResult.user).toBeUndefined(); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.state).toBeUndefined(); expect(authenticationResult.error).toBeUndefined(); expect(authenticationResult.authHeaders).toBeUndefined(); @@ -47,6 +49,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.error).toBe(failureReason); expect(authenticationResult.user).toBeUndefined(); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.state).toBeUndefined(); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBeUndefined(); @@ -67,6 +70,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.authResponseHeaders).toEqual({ 'WWW-Authenticate': 'Negotiate' }); expect(authenticationResult.error).toBe(failureReason); expect(authenticationResult.user).toBeUndefined(); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.state).toBeUndefined(); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.redirectURL).toBeUndefined(); @@ -80,7 +84,7 @@ describe('AuthenticationResult', () => { ); }); - it('correctly produces `succeeded` authentication result without state and auth headers.', () => { + it('correctly produces `succeeded` authentication result without state, auth headers and user profile grant.', () => { const user = mockAuthenticatedUser(); const authenticationResult = AuthenticationResult.succeeded(user); @@ -90,6 +94,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.redirected()).toBe(false); expect(authenticationResult.user).toBe(user); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.state).toBeUndefined(); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBeUndefined(); @@ -97,7 +102,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.redirectURL).toBeUndefined(); }); - it('correctly produces `succeeded` authentication result with state, but without auth headers.', () => { + it('correctly produces `succeeded` authentication result with state, but without user profile grant and auth headers.', () => { const user = mockAuthenticatedUser(); const state = { some: 'state' }; const authenticationResult = AuthenticationResult.succeeded(user, { state }); @@ -109,13 +114,40 @@ describe('AuthenticationResult', () => { expect(authenticationResult.user).toBe(user); expect(authenticationResult.state).toBe(state); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBeUndefined(); expect(authenticationResult.error).toBeUndefined(); expect(authenticationResult.redirectURL).toBeUndefined(); }); - it('correctly produces `succeeded` authentication result with auth headers, but without state.', () => { + it('correctly produces `succeeded` authentication result with state and user profile grant, but without auth headers.', () => { + const user = mockAuthenticatedUser(); + const state = { some: 'state' }; + const userProfileGrant = { + type: 'accessToken' as 'accessToken', + accessToken: 'access-token', + }; + const authenticationResult = AuthenticationResult.succeeded(user, { + userProfileGrant, + state, + }); + + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.failed()).toBe(false); + expect(authenticationResult.notHandled()).toBe(false); + expect(authenticationResult.redirected()).toBe(false); + + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.state).toBe(state); + expect(authenticationResult.userProfileGrant).toBe(userProfileGrant); + expect(authenticationResult.authHeaders).toBeUndefined(); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.error).toBeUndefined(); + expect(authenticationResult.redirectURL).toBeUndefined(); + }); + + it('correctly produces `succeeded` authentication result with auth headers, but without state and user profile grant.', () => { const user = mockAuthenticatedUser(); const authHeaders = { authorization: 'some-token' }; const authResponseHeaders = { 'WWW-Authenticate': 'Negotiate' }; @@ -130,6 +162,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.redirected()).toBe(false); expect(authenticationResult.user).toBe(user); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.state).toBeUndefined(); expect(authenticationResult.authHeaders).toBe(authHeaders); expect(authenticationResult.authResponseHeaders).toBe(authResponseHeaders); @@ -137,14 +170,20 @@ describe('AuthenticationResult', () => { expect(authenticationResult.redirectURL).toBeUndefined(); }); - it('correctly produces `succeeded` authentication result with both auth headers and state.', () => { + it('correctly produces `succeeded` authentication result with auth headers, state and user profile grant.', () => { const user = mockAuthenticatedUser(); const authHeaders = { authorization: 'some-token' }; const authResponseHeaders = { 'WWW-Authenticate': 'Negotiate' }; const state = { some: 'state' }; + const userProfileGrant: UserProfileGrant = { + type: 'password', + username: 'user', + password: 'password', + }; const authenticationResult = AuthenticationResult.succeeded(user, { authHeaders, authResponseHeaders, + userProfileGrant, state, }); @@ -157,6 +196,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.state).toBe(state); expect(authenticationResult.authHeaders).toBe(authHeaders); expect(authenticationResult.authResponseHeaders).toBe(authResponseHeaders); + expect(authenticationResult.userProfileGrant).toBe(userProfileGrant); expect(authenticationResult.error).toBeUndefined(); expect(authenticationResult.redirectURL).toBeUndefined(); }); @@ -169,7 +209,7 @@ describe('AuthenticationResult', () => { ); }); - it('correctly produces `redirected` authentication result without state, user and response headers.', () => { + it('correctly produces `redirected` authentication result without state, user, user profile grant and response headers.', () => { const redirectURL = '/redirect/url'; const authenticationResult = AuthenticationResult.redirectTo(redirectURL); @@ -183,6 +223,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.state).toBeUndefined(); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.error).toBeUndefined(); }); @@ -200,6 +241,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.state).toBe(state); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.user).toBeUndefined(); expect(authenticationResult.error).toBeUndefined(); }); @@ -219,17 +261,24 @@ describe('AuthenticationResult', () => { expect(authenticationResult.state).toBe(state); expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.userProfileGrant).toBeUndefined(); expect(authenticationResult.user).toBe(user); expect(authenticationResult.error).toBeUndefined(); }); - it('correctly produces `redirected` authentication result with state, user and response headers.', () => { + it('correctly produces `redirected` authentication result with state, user, user profile grant and response headers.', () => { const redirectURL = '/redirect/url'; const state = { some: 'state' }; const user = mockAuthenticatedUser(); const authResponseHeaders = { 'WWW-Authenticate': 'Negotiate' }; + const userProfileGrant: UserProfileGrant = { + type: 'password', + username: 'user', + password: 'password', + }; const authenticationResult = AuthenticationResult.redirectTo(redirectURL, { user, + userProfileGrant, state, authResponseHeaders, }); @@ -244,6 +293,7 @@ describe('AuthenticationResult', () => { expect(authenticationResult.authHeaders).toBeUndefined(); expect(authenticationResult.authResponseHeaders).toBe(authResponseHeaders); expect(authenticationResult.user).toBe(user); + expect(authenticationResult.userProfileGrant).toBe(userProfileGrant); expect(authenticationResult.error).toBeUndefined(); }); }); diff --git a/x-pack/plugins/security/server/authentication/authentication_result.ts b/x-pack/plugins/security/server/authentication/authentication_result.ts index 842689b99a446..62b93a0f0c337 100644 --- a/x-pack/plugins/security/server/authentication/authentication_result.ts +++ b/x-pack/plugins/security/server/authentication/authentication_result.ts @@ -7,7 +7,8 @@ import type { AuthHeaders } from '@kbn/core/server'; -import type { AuthenticatedUser } from '../../common/model'; +import type { AuthenticatedUser } from '../../common'; +import type { UserProfileGrant } from '../user_profile'; /** * Represents status that `AuthenticationResult` can be in. @@ -49,6 +50,25 @@ interface AuthenticationOptions { user?: AuthenticatedUser; authHeaders?: AuthHeaders; authResponseHeaders?: AuthHeaders; + userProfileGrant?: UserProfileGrant; +} + +export interface SucceededAuthenticationResultOptions { + state?: unknown; + authHeaders?: AuthHeaders; + authResponseHeaders?: AuthHeaders; + userProfileGrant?: UserProfileGrant; +} + +export interface RedirectedAuthenticationResultOptions { + state?: unknown; + user?: AuthenticatedUser; + authResponseHeaders?: AuthHeaders; + userProfileGrant?: UserProfileGrant; +} + +export interface FailedAuthenticationResultOptions { + authResponseHeaders?: AuthHeaders; } /** @@ -66,6 +86,7 @@ export class AuthenticationResult { /** * Produces `AuthenticationResult` for the case when authentication succeeds. * @param user User information retrieved as a result of successful authentication attempt. + * @param [userProfileGrant] Optional user profile grant that can be used to activate user profile. * @param [authHeaders] Optional dictionary of the HTTP headers with authentication information. * @param [authResponseHeaders] Optional dictionary of the HTTP headers with authentication * information that should be specified in the response we send to the client request. @@ -74,10 +95,11 @@ export class AuthenticationResult { public static succeeded( user: AuthenticatedUser, { + userProfileGrant, authHeaders, authResponseHeaders, state, - }: Pick = {} + }: SucceededAuthenticationResultOptions = {} ) { if (!user) { throw new Error('User should be specified.'); @@ -85,6 +107,7 @@ export class AuthenticationResult { return new AuthenticationResult(AuthenticationResultStatus.Succeeded, { user, + userProfileGrant, authHeaders, authResponseHeaders, state, @@ -100,7 +123,7 @@ export class AuthenticationResult { */ public static failed( error: Error, - { authResponseHeaders }: Pick = {} + { authResponseHeaders }: FailedAuthenticationResultOptions = {} ) { if (!error) { throw new Error('Error should be specified.'); @@ -116,6 +139,7 @@ export class AuthenticationResult { * Produces `AuthenticationResult` for the case when authentication needs user to be redirected. * @param redirectURL URL that should be used to redirect user to complete authentication. * @param [user] Optional user information retrieved as a result of successful authentication attempt. + * @param [userProfileGrant] Optional user profile grant that can be used to activate user profile. * @param [authResponseHeaders] Optional dictionary of the HTTP headers with authentication * information that should be specified in the response we send to the client request. * @param [state] Optional state to be stored and reused for the next request. @@ -124,9 +148,10 @@ export class AuthenticationResult { redirectURL: string, { user, + userProfileGrant, authResponseHeaders, state, - }: Pick = {} + }: RedirectedAuthenticationResultOptions = {} ) { if (!redirectURL) { throw new Error('Redirect URL must be specified.'); @@ -135,18 +160,26 @@ export class AuthenticationResult { return new AuthenticationResult(AuthenticationResultStatus.Redirected, { redirectURL, user, + userProfileGrant, authResponseHeaders, state, }); } /** - * Authenticated user instance (only available for `succeeded` result). + * Authenticated user instance (only available for `succeeded` or `redirected` result). */ public get user() { return this.options.user; } + /** + * User profile grant that can be used to activate user profile (only available for `succeeded` and `redirected` results). + */ + public get userProfileGrant() { + return this.options.userProfileGrant; + } + /** * Headers that include authentication information that should be used to authenticate user for any * future requests (only available for `succeeded` result). @@ -169,8 +202,7 @@ export class AuthenticationResult { } /** - * State associated with the authenticated user (only available for `succeeded` - * and `redirected` results). + * State associated with the authenticated user (only available for `succeeded` and `redirected` results). */ public get state() { return this.options.state; @@ -184,8 +216,7 @@ export class AuthenticationResult { } /** - * URL that should be used to redirect user to complete authentication only available - * for `redirected` result). + * URL that should be used to redirect user to complete authentication (only available for `redirected` result). */ public get redirectURL() { return this.options.redirectURL; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index e071839f20351..fc69971649330 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -49,6 +49,7 @@ import { securityMock } from '../mocks'; import { ROUTE_TAG_AUTH_FLOW } from '../routes/tags'; import type { Session } from '../session_management'; import { sessionMock } from '../session_management/session.mock'; +import { userProfileServiceMock } from '../user_profile/user_profile_service.mock'; import { AuthenticationResult } from './authentication_result'; import { AuthenticationService } from './authentication_service'; @@ -69,9 +70,11 @@ describe('AuthenticationService', () => { http: jest.Mocked; clusterClient: ReturnType; featureUsageService: jest.Mocked; + userProfileService: ReturnType; session: jest.Mocked>; applicationName: 'kibana-.kibana'; kibanaFeatures: []; + isElasticCloudDeployment: jest.Mock; }; beforeEach(() => { logger = loggingSystemMock.createLogger(); @@ -110,8 +113,10 @@ describe('AuthenticationService', () => { loggers: loggingSystemMock.create(), featureUsageService: securityFeatureUsageServiceMock.createStartContract(), session: sessionMock.create(), + userProfileService: userProfileServiceMock.createStart(), applicationName: 'kibana-.kibana', kibanaFeatures: [], + isElasticCloudDeployment: jest.fn().mockReturnValue(false), }; (mockStartAuthenticationParams.http.basePath.get as jest.Mock).mockImplementation( () => mockStartAuthenticationParams.http.basePath.serverBasePath diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 0318fe3823a46..50ad8ae2b082b 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -26,6 +26,7 @@ import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; import { ROUTE_TAG_AUTH_FLOW } from '../routes/tags'; import type { Session } from '../session_management'; +import type { UserProfileServiceStart } from '../user_profile'; import { APIKeys } from './api_keys'; import type { AuthenticationResult } from './authentication_result'; import type { ProviderLoginAttempt } from './authenticator'; @@ -48,10 +49,12 @@ interface AuthenticationServiceStartParams { clusterClient: IClusterClient; audit: AuditServiceSetup; featureUsageService: SecurityFeatureUsageServiceStart; + userProfileService: UserProfileServiceStart; session: PublicMethodsOf; loggers: LoggerFactory; applicationName: string; kibanaFeatures: KibanaFeature[]; + isElasticCloudDeployment: () => boolean; } export interface InternalAuthenticationServiceStart extends AuthenticationServiceStart { @@ -296,11 +299,13 @@ export class AuthenticationService { config, clusterClient, featureUsageService, + userProfileService, http, loggers, session, applicationName, kibanaFeatures, + isElasticCloudDeployment, }: AuthenticationServiceStartParams): InternalAuthenticationServiceStart { const apiKeys = new APIKeys({ clusterClient, @@ -333,9 +338,11 @@ export class AuthenticationService { config: { authc: config.authc }, getCurrentUser, featureUsageService, + userProfileService, getServerBaseURL, license: this.license, session, + isElasticCloudDeployment, }); return { diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 3bbb1e5a48be2..43a85603edbb9 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -20,13 +20,14 @@ import { } from '@kbn/core/server/mocks'; import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { SecurityLicenseFeatures } from '../../common'; import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, AUTH_URL_HASH_QUERY_STRING_PARAMETER, } from '../../common/constants'; -import type { SecurityLicenseFeatures } from '../../common/licensing'; import { licenseMock } from '../../common/licensing/index.mock'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; +import { userProfileMock } from '../../common/model/user_profile.mock'; import type { AuditLogger } from '../audit'; import { auditLoggerMock, auditServiceMock } from '../audit/mocks'; import { ConfigSchema, createConfig } from '../config'; @@ -34,6 +35,8 @@ import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock'; import { securityMock } from '../mocks'; import type { SessionValue } from '../session_management'; import { sessionMock } from '../session_management/index.mock'; +import type { UserProfileGrant } from '../user_profile'; +import { userProfileServiceMock } from '../user_profile/user_profile_service.mock'; import { AuthenticationResult } from './authentication_result'; import type { AuthenticatorOptions } from './authenticator'; import { Authenticator } from './authenticator'; @@ -68,6 +71,8 @@ function getMockOptions({ ), session: sessionMock.create(), featureUsageService: securityFeatureUsageServiceMock.createStartContract(), + userProfileService: userProfileServiceMock.createStart(), + isElasticCloudDeployment: jest.fn().mockReturnValue(false), }; } @@ -430,6 +435,39 @@ describe('Authenticator', () => { state: { authorization }, }); expectAuditEvents({ action: 'user_login', outcome: 'success' }); + expect(mockOptions.userProfileService.activate).not.toHaveBeenCalled(); + }); + + it('activates profile whenever authentication provider returns user profile grant', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest(); + const authorization = `Basic ${Buffer.from('foo:bar').toString('base64')}`; + const userProfileGrant: UserProfileGrant = { + type: 'password', + username: 'some-user', + password: 'some-password', + }; + + mockBasicAuthenticationProvider.login.mockResolvedValue( + AuthenticationResult.succeeded(user, { userProfileGrant, state: { authorization } }) + ); + + await expect( + authenticator.login(request, { provider: { type: 'basic' }, value: {} }) + ).resolves.toEqual( + AuthenticationResult.succeeded(user, { userProfileGrant, state: { authorization } }) + ); + + expect(mockOptions.session.create).toHaveBeenCalledTimes(1); + expect(mockOptions.session.create).toHaveBeenCalledWith(request, { + username: user.username, + userProfileId: 'some-profile-uid', + provider: mockSessVal.provider, + state: { authorization }, + }); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); + expect(mockOptions.userProfileService.activate).toHaveBeenCalledTimes(1); + expect(mockOptions.userProfileService.activate).toHaveBeenCalledWith(userProfileGrant); }); it('returns `notHandled` if login attempt is targeted to not configured provider.', async () => { @@ -1252,6 +1290,7 @@ describe('Authenticator', () => { state: { authorization }, }); expect(auditLogger.log).not.toHaveBeenCalled(); + expect(mockOptions.userProfileService.activate).not.toHaveBeenCalled(); }); it('creates session whenever authentication provider returns state for non-system API requests', async () => { @@ -1276,6 +1315,39 @@ describe('Authenticator', () => { state: { authorization }, }); expect(auditLogger.log).not.toHaveBeenCalled(); + expect(mockOptions.userProfileService.activate).not.toHaveBeenCalled(); + }); + + it('activates user profile whenever authentication provider returns user profile grant for non-system API requests', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest({ + headers: { 'kbn-system-request': 'false' }, + }); + const authorization = `Basic ${Buffer.from('foo:bar').toString('base64')}`; + const userProfileGrant: UserProfileGrant = { + type: 'password', + username: 'some-user', + password: 'some-password', + }; + + mockBasicAuthenticationProvider.authenticate.mockResolvedValue( + AuthenticationResult.succeeded(user, { userProfileGrant, state: { authorization } }) + ); + + await expect(authenticator.authenticate(request)).resolves.toEqual( + AuthenticationResult.succeeded(user, { userProfileGrant, state: { authorization } }) + ); + + expect(mockOptions.session.create).toHaveBeenCalledTimes(1); + expect(mockOptions.session.create).toHaveBeenCalledWith(request, { + username: user.username, + userProfileId: 'some-profile-uid', + provider: mockSessVal.provider, + state: { authorization }, + }); + expect(auditLogger.log).not.toHaveBeenCalled(); + expect(mockOptions.userProfileService.activate).toHaveBeenCalledTimes(1); + expect(mockOptions.userProfileService.activate).toHaveBeenCalledWith(userProfileGrant); }); it('does not extend session for system API calls.', async () => { @@ -1392,6 +1464,7 @@ describe('Authenticator', () => { expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); expect(auditLogger.log).not.toHaveBeenCalled(); + expect(mockOptions.userProfileService.activate).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider for non-system API requests', async () => { @@ -1419,6 +1492,46 @@ describe('Authenticator', () => { expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); expect(auditLogger.log).not.toHaveBeenCalled(); + expect(mockOptions.userProfileService.activate).not.toHaveBeenCalled(); + }); + + it('re-activates user profile if authentication provider returns a user profile grant for non-system API requests', async () => { + const user = mockAuthenticatedUser(); + const newState = { authorization: 'Basic yyy' }; + const request = httpServerMock.createKibanaRequest({ + headers: { 'kbn-system-request': 'false' }, + }); + const userProfileGrant: UserProfileGrant = { + type: 'password', + username: 'some-user', + password: 'some-password', + }; + mockOptions.userProfileService.activate.mockResolvedValue({ + ...userProfileMock.create(), + uid: 'new-profile-uid', + }); + + mockBasicAuthenticationProvider.authenticate.mockResolvedValue( + AuthenticationResult.succeeded(user, { userProfileGrant, state: newState }) + ); + mockOptions.session.get.mockResolvedValue(mockSessVal); + + await expect(authenticator.authenticate(request)).resolves.toEqual( + AuthenticationResult.succeeded(user, { userProfileGrant, state: newState }) + ); + + expect(mockOptions.session.update).toHaveBeenCalledTimes(1); + expect(mockOptions.session.update).toHaveBeenCalledWith(request, { + ...mockSessVal, + userProfileId: 'new-profile-uid', + state: newState, + }); + expect(mockOptions.session.create).not.toHaveBeenCalled(); + expect(mockOptions.session.extend).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); + expect(mockOptions.userProfileService.activate).toHaveBeenCalledTimes(1); + expect(mockOptions.userProfileService.activate).toHaveBeenCalledWith(userProfileGrant); }); it('clears session if provider failed to authenticate system API request with 401 with active session.', async () => { diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 7164b5e98b2f6..7f42bde1e397d 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -10,6 +10,7 @@ import { KibanaRequest } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { AuthenticatedUser, AuthenticationProvider, SecurityLicense } from '../../common'; import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, AUTH_URL_HASH_QUERY_STRING_PARAMETER, @@ -17,8 +18,6 @@ import { LOGOUT_REASON_QUERY_STRING_PARAMETER, NEXT_URL_QUERY_STRING_PARAMETER, } from '../../common/constants'; -import type { SecurityLicense } from '../../common/licensing'; -import type { AuthenticatedUser, AuthenticationProvider } from '../../common/model'; import { shouldProviderUseLoginForm } from '../../common/model'; import type { AuditServiceSetup } from '../audit'; import { accessAgreementAcknowledgedEvent, userLoginEvent, userLogoutEvent } from '../audit'; @@ -26,6 +25,7 @@ import type { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; import type { Session, SessionValue } from '../session_management'; +import type { UserProfileServiceStart } from '../user_profile'; import { AuthenticationResult } from './authentication_result'; import { canRedirectRequest } from './can_redirect_request'; import { DeauthenticationResult } from './deauthentication_result'; @@ -80,6 +80,7 @@ export interface ProviderLoginAttempt { export interface AuthenticatorOptions { audit: AuditServiceSetup; featureUsageService: SecurityFeatureUsageServiceStart; + userProfileService: UserProfileServiceStart; getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; config: Pick; basePath: IBasePath; @@ -88,6 +89,7 @@ export interface AuthenticatorOptions { clusterClient: IClusterClient; session: PublicMethodsOf; getServerBaseURL: () => string; + isElasticCloudDeployment: () => boolean; } /** @internal */ @@ -231,6 +233,7 @@ export class Authenticator { logger: this.options.loggers.get('tokens'), }), getServerBaseURL: this.options.getServerBaseURL, + isElasticCloudDeployment: this.options.isElasticCloudDeployment, }; this.providers = new Map( @@ -712,16 +715,35 @@ export class Authenticator { existingSessionValue = null; } + // If authentication result includes user profile grant, we should try to activate user profile for this user and + // store user profile identifier in the session value. + let userProfileId = existingSessionValue?.userProfileId; + if (authenticationResult.userProfileGrant) { + this.logger.debug(`Activating profile for "${authenticationResult.user?.username}".`); + userProfileId = ( + await this.options.userProfileService.activate(authenticationResult.userProfileGrant) + ).uid; + + if ( + existingSessionValue?.userProfileId && + existingSessionValue.userProfileId !== userProfileId + ) { + this.logger.warn(`User profile for "${authenticationResult.user?.username}" has changed.`); + } + } + let newSessionValue; if (!existingSessionValue) { newSessionValue = await this.session.create(request, { username: authenticationResult.user?.username, + userProfileId, provider, state: authenticationResult.shouldUpdateState() ? authenticationResult.state : null, }); } else if (authenticationResult.shouldUpdateState()) { newSessionValue = await this.session.update(request, { ...existingSessionValue, + userProfileId, state: authenticationResult.shouldUpdateState() ? authenticationResult.state : existingSessionValue.state, diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index 2abe3cf6277a5..af7537ca15074 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -27,5 +27,6 @@ export function mockAuthenticationProviderOptions(options?: { name: string }) { urls: { loggedOut: jest.fn().mockReturnValue('/mock-server-basepath/security/logged_out'), }, + isElasticCloudDeployment: jest.fn().mockReturnValue(false), }; } diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index a344243ba97a7..ccf9ecba71f36 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -38,6 +38,7 @@ export interface AuthenticationProviderOptions { urls: { loggedOut: (request: KibanaRequest) => string; }; + isElasticCloudDeployment: () => boolean; } /** @@ -45,6 +46,11 @@ export interface AuthenticationProviderOptions { */ export type AuthenticationProviderSpecificOptions = Record; +/** + * Name of the Elastic Cloud built-in SSO realm. + */ +export const ELASTIC_CLOUD_SSO_REALM_NAME = 'cloud-saml-kibana'; + /** * Base class that all authentication providers should extend. */ @@ -133,6 +139,10 @@ export abstract class BaseAuthenticationProvider { return deepFreeze({ ...authenticationInfo, authentication_provider: { type: this.type, name: this.options.name }, + elastic_cloud_user: + this.options.isElasticCloudDeployment() && + authenticationInfo.authentication_realm.type === 'saml' && + authenticationInfo.authentication_realm.name === ELASTIC_CLOUD_SSO_REALM_NAME, } as AuthenticatedUser); } } diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts index 3fbaa82f15cdc..29bf38c6f1653 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts @@ -57,6 +57,7 @@ describe('BasicAuthenticationProvider', () => { ).resolves.toEqual( AuthenticationResult.succeeded(user, { authHeaders: { authorization }, + userProfileGrant: { type: 'password', username: 'user', password: 'password' }, state: { authorization }, }) ); diff --git a/x-pack/plugins/security/server/authentication/providers/basic.ts b/x-pack/plugins/security/server/authentication/providers/basic.ts index 02094d680fb07..cbdcfbe9a5ead 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.ts @@ -80,7 +80,11 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { const user = await this.getUser(request, authHeaders); this.logger.debug('Login has been successfully performed.'); - return AuthenticationResult.succeeded(user, { authHeaders, state: authHeaders }); + return AuthenticationResult.succeeded(user, { + userProfileGrant: { type: 'password', username, password }, + authHeaders, + state: authHeaders, + }); } catch (err) { this.logger.debug(`Failed to perform a login: ${err.message}`); return AuthenticationResult.failed(err); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 2db1d149962b1..8643386f762b3 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -135,6 +135,7 @@ describe('KerberosAuthenticationProvider', () => { { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer some-token' }, + userProfileGrant: { type: 'accessToken', accessToken: 'some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' }, } ) @@ -170,6 +171,7 @@ describe('KerberosAuthenticationProvider', () => { { authHeaders: { authorization: 'Bearer some-token' }, authResponseHeaders: { 'WWW-Authenticate': 'Negotiate response-token' }, + userProfileGrant: { type: 'accessToken', accessToken: 'some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' }, } ) diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 9b793e886b74c..5cf7ede569e5d 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -217,6 +217,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.succeeded( this.authenticationInfoToAuthenticatedUser(tokens.authentication), { + userProfileGrant: { type: 'accessToken', accessToken: tokens.access_token }, authHeaders: { authorization: new HTTPAuthorizationHeader('Bearer', tokens.access_token).toString(), }, diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 097491d61d4ca..c02f7c54c5421 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -201,6 +201,7 @@ describe('OIDCAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo('/base-path/some-path', { + userProfileGrant: { type: 'accessToken', accessToken: 'some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token', diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index 44aa56bd2a298..143245b31770b 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -275,12 +275,13 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Login has been performed with OpenID Connect response.'); return AuthenticationResult.redirectTo(stateRedirectURL, { + user: this.authenticationInfoToAuthenticatedUser(result.authentication), + userProfileGrant: { type: 'accessToken', accessToken: result.access_token }, state: { accessToken: result.access_token, refreshToken: result.refresh_token, realm: this.realm, }, - user: this.authenticationInfoToAuthenticatedUser(result.authentication), }); } diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index b099ad87460de..0196ec8d99421 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -261,6 +261,7 @@ describe('PKIAuthenticationProvider', () => { { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, + userProfileGrant: { type: 'accessToken', accessToken: 'access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, } ) @@ -305,6 +306,7 @@ describe('PKIAuthenticationProvider', () => { { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, + userProfileGrant: { type: 'accessToken', accessToken: 'access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, } ) @@ -342,6 +344,7 @@ describe('PKIAuthenticationProvider', () => { { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, + userProfileGrant: { type: 'accessToken', accessToken: 'access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, } ) @@ -508,6 +511,7 @@ describe('PKIAuthenticationProvider', () => { { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, + userProfileGrant: { type: 'accessToken', accessToken: 'access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, } ) diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index adba9efd3e81e..0e3544063f1d6 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -42,7 +42,7 @@ interface CertificateChain { /** * List of protocols that can be renegotiated. Notably, TLSv1.3 is absent from this list, because it does not support renegotiation. */ -const RENEGOTIATABLE_PROTOCOLS = ['TLSv1', 'TLSv1.1', 'TLSv1.2']; +const RENEGOTIABLE_PROTOCOLS = ['TLSv1', 'TLSv1.1', 'TLSv1.2']; /** * Checks whether current request can initiate new session. @@ -133,7 +133,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { (authenticationResult.failed() && Tokens.isAccessTokenExpiredError(authenticationResult.error)); if (invalidAccessToken) { - authenticationResult = await this.authenticateViaPeerCertificate(request); + authenticationResult = await this.authenticateViaPeerCertificate(request, state); // If we have an active session that we couldn't use to authenticate user and at the same time // we couldn't use peer's certificate to establish a new one, then we should respond with 401 // and force authenticator to clear the session. @@ -147,7 +147,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { // to start a new session, and if so try to authenticate request using its peer certificate chain, // otherwise just return authentication result we have. return authenticationResult.notHandled() && canStartNewSession(request) - ? await this.authenticateViaPeerCertificate(request) + ? await this.authenticateViaPeerCertificate(request, state) : authenticationResult; } @@ -247,8 +247,12 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { /** * Tries to exchange peer certificate chain to access/refresh token pair. * @param request Request instance. + * @param [state] Optional state object associated with the provider. */ - private async authenticateViaPeerCertificate(request: KibanaRequest) { + private async authenticateViaPeerCertificate( + request: KibanaRequest, + state?: ProviderState | null + ) { this.logger.debug('Trying to authenticate request via peer certificate chain.'); // We should collect entire certificate chain as an ordered array of certificates encoded as base64 strings. @@ -293,6 +297,11 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.succeeded( this.authenticationInfoToAuthenticatedUser(result.authentication), { + // There is no need to re-activate user profile if client certificate hasn't changed. + userProfileGrant: + peerCertificate.fingerprint256 !== state?.peerCertificateFingerprint256 + ? { type: 'accessToken', accessToken: result.access_token } + : undefined, authHeaders: { authorization: new HTTPAuthorizationHeader('Bearer', result.access_token).toString(), }, @@ -311,6 +320,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { * (root/self-signed certificate) or when `issuerCertificate` isn't available (null or empty object). Automatically attempts to * renegotiate the TLS connection once if the peer certificate chain is incomplete. * @param request Request instance. + * @param isRenegotiated Indicates whether connection has been already renegotiated. */ private async getCertificateChain( request: KibanaRequest, @@ -332,13 +342,13 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { break; } else if (certificate.issuerCertificate === undefined) { const protocol = request.socket.getProtocol(); - if (!isRenegotiated && protocol && RENEGOTIATABLE_PROTOCOLS.includes(protocol)) { + if (!isRenegotiated && protocol && RENEGOTIABLE_PROTOCOLS.includes(protocol)) { this.logger.debug( `Detected incomplete certificate chain with protocol '${protocol}', attempting to renegotiate connection.` ); try { await request.socket.renegotiate({ requestCert: true, rejectUnauthorized: false }); - return this.getCertificateChain(request, true); + return this.getCertificateChain(request, true /* isRenegotiated */); } catch (err) { this.logger.debug(`Failed to renegotiate connection: ${err}.`); } diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 07cc8a9d1bb07..a165b1960c3f3 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -18,6 +18,7 @@ import { mockAuthenticatedUser } from '../../../common/model/authenticated_user. import { securityMock } from '../../mocks'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; +import { ELASTIC_CLOUD_SSO_REALM_NAME } from './base'; import type { MockAuthenticationProviderOptions } from './base.mock'; import { mockAuthenticationProviderOptions } from './base.mock'; import { SAMLAuthenticationProvider, SAMLLogin } from './saml'; @@ -63,6 +64,7 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo('/test-base-path/some-path#some-app', { + userProfileGrant: { type: 'accessToken', accessToken: 'some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token', @@ -108,6 +110,7 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo('/test-base-path/some-path#some-app', { + userProfileGrant: { type: 'accessToken', accessToken: 'some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token', @@ -184,6 +187,7 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo('/mock-server-basepath/', { + userProfileGrant: { type: 'accessToken', accessToken: 'user-initiated-login-token' }, state: { accessToken: 'user-initiated-login-token', refreshToken: 'user-initiated-login-refresh-token', @@ -225,6 +229,7 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo('/mock-server-basepath/', { + userProfileGrant: { type: 'accessToken', accessToken: 'user-initiated-login-token' }, state: { accessToken: 'user-initiated-login-token', refreshToken: 'user-initiated-login-refresh-token', @@ -258,6 +263,7 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo('/mock-server-basepath/', { + userProfileGrant: { type: 'accessToken', accessToken: 'idp-initiated-login-token' }, state: { accessToken: 'idp-initiated-login-token', refreshToken: 'idp-initiated-login-refresh-token', @@ -331,6 +337,7 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + userProfileGrant: { type: 'accessToken', accessToken: 'valid-token' }, state: { accessToken: 'valid-token', refreshToken: 'valid-refresh-token', @@ -349,6 +356,7 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + userProfileGrant: { type: 'accessToken', accessToken: 'valid-token' }, state: { accessToken: 'valid-token', refreshToken: 'valid-refresh-token', @@ -359,6 +367,43 @@ describe('SAMLAuthenticationProvider', () => { ); }); + it('recognizes Elastic Cloud users.', async () => { + const nonElasticCloudUser = mockAuthenticatedUser({ + authentication_provider: { type: 'saml', name: 'saml' }, + authentication_realm: { type: 'saml', name: 'random-saml' }, + }); + const elasticCloudUser = mockAuthenticatedUser({ + authentication_provider: { type: 'saml', name: 'saml' }, + authentication_realm: { type: 'saml', name: ELASTIC_CLOUD_SSO_REALM_NAME }, + }); + + // The only case when user should be recognized as Elastic Cloud user: Kibana is running inside Cloud + // deployment and user is authenticated with SAML realm of the predefined name. + for (const [authentication, isElasticCloudDeployment, isElasticCloudUser] of [ + [nonElasticCloudUser, false, false], + [nonElasticCloudUser, true, false], + [elasticCloudUser, false, false], + [elasticCloudUser, true, true], + ]) { + mockOptions.client.asInternalUser.transport.request.mockResolvedValue({ + username: 'user', + access_token: 'valid-token', + refresh_token: 'valid-refresh-token', + realm: 'test-realm', + authentication, + }); + + mockOptions.isElasticCloudDeployment.mockReturnValue(isElasticCloudDeployment); + + const loginResult = await provider.login( + httpServerMock.createKibanaRequest({ headers: {} }), + { type: SAMLLogin.LoginWithSAMLResponse, samlResponse: 'saml-response-xml' } + ); + + expect(loginResult.user?.elastic_cloud_user).toBe(isElasticCloudUser); + } + }); + it('redirects to the home page if `relayState` includes external URL', async () => { await expect( provider.login(httpServerMock.createKibanaRequest({ headers: {} }), { @@ -368,6 +413,7 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + userProfileGrant: { type: 'accessToken', accessToken: 'valid-token' }, state: { accessToken: 'valid-token', refreshToken: 'valid-refresh-token', @@ -387,6 +433,7 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + userProfileGrant: { type: 'accessToken', accessToken: 'valid-token' }, state: { accessToken: 'valid-token', refreshToken: 'valid-refresh-token', @@ -408,6 +455,7 @@ describe('SAMLAuthenticationProvider', () => { AuthenticationResult.redirectTo( `${mockOptions.basePath.serverBasePath}/app/some-app#some-deep-link`, { + userProfileGrant: { type: 'accessToken', accessToken: 'valid-token' }, state: { accessToken: 'valid-token', refreshToken: 'valid-refresh-token', @@ -432,6 +480,7 @@ describe('SAMLAuthenticationProvider', () => { }) ).resolves.toEqual( AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, { + userProfileGrant: { type: 'accessToken', accessToken: 'valid-token' }, state: { accessToken: 'valid-token', refreshToken: 'valid-refresh-token', @@ -601,6 +650,7 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo('/mock-server-basepath/', { + userProfileGrant: { type: 'accessToken', accessToken: 'new-valid-token' }, state: { accessToken: 'new-valid-token', refreshToken: 'new-valid-refresh-token', @@ -663,6 +713,7 @@ describe('SAMLAuthenticationProvider', () => { ) ).resolves.toEqual( AuthenticationResult.redirectTo('/mock-server-basepath/app/some-app#some-deep-link', { + userProfileGrant: { type: 'accessToken', accessToken: 'new-valid-token' }, state: { accessToken: 'new-valid-token', refreshToken: 'new-valid-refresh-token', diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 0adb2fb565996..890e76301ddc7 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -409,12 +409,13 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { return AuthenticationResult.redirectTo( redirectURLFromRelayState || stateRedirectURL || `${this.options.basePath.get(request)}/`, { + user: this.authenticationInfoToAuthenticatedUser(result.authentication), + userProfileGrant: { type: 'accessToken', accessToken: result.access_token }, state: { accessToken: result.access_token, refreshToken: result.refresh_token, realm: result.realm, }, - user: this.authenticationInfoToAuthenticatedUser(result.authentication), } ); } diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 30ca1c2561123..fbdf6e39abff3 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -61,7 +61,11 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.login(request, credentials)).resolves.toEqual( AuthenticationResult.succeeded( { ...user, authentication_provider: { type: 'token', name: 'token' } }, - { authHeaders: { authorization }, state: tokenPair } + { + authHeaders: { authorization }, + userProfileGrant: { type: 'accessToken', accessToken: tokenPair.accessToken }, + state: tokenPair, + } ) ); diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 1bbf6605ff135..d34704c53260b 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -86,6 +86,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { authenticationInfo as AuthenticationInfo ), { + userProfileGrant: { type: 'accessToken', accessToken }, authHeaders: { authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(), }, diff --git a/x-pack/plugins/security/server/elasticsearch/index.ts b/x-pack/plugins/security/server/elasticsearch/index.ts index 46acb874f7fc2..239802028b122 100644 --- a/x-pack/plugins/security/server/elasticsearch/index.ts +++ b/x-pack/plugins/security/server/elasticsearch/index.ts @@ -7,7 +7,10 @@ import type { AuthenticatedUser } from '../../common/model'; -export type AuthenticationInfo = Omit; +export type AuthenticationInfo = Omit< + AuthenticatedUser, + 'authentication_provider' | 'elastic_cloud_user' +>; export type { ElasticsearchServiceStart, OnlineStatusRetryScheduler, diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index de484647ffb6d..d126efb4d79bd 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -31,6 +31,7 @@ function createSetupMock() { privilegeDeprecationsService: { getKibanaRolesByFeatureId: jest.fn(), }, + setIsElasticCloudDeployment: jest.fn(), }; } diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index c1fddb1751189..663f0f16ea260 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -124,9 +124,18 @@ describe('Security Plugin', () => { "privilegeDeprecationsService": Object { "getKibanaRolesByFeatureId": [Function], }, + "setIsElasticCloudDeployment": [Function], } `); }); + + it('#setIsElasticCloudDeployment cannot be called twice', () => { + const { setIsElasticCloudDeployment } = plugin.setup(mockCoreSetup, mockSetupDependencies); + setIsElasticCloudDeployment(); + expect(() => setIsElasticCloudDeployment()).toThrowErrorMatchingInlineSnapshot( + `"The Elastic Cloud deployment flag has been set already!"` + ); + }); }); describe('start()', () => { diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 07b3e1ea232ec..c470147acb957 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -55,6 +55,8 @@ import type { Session } from './session_management'; import { SessionManagementService } from './session_management'; import { setupSpacesClient } from './spaces'; import { registerSecurityUsageCollector } from './usage_collector'; +import { UserProfileService } from './user_profile'; +import type { UserProfileServiceStart } from './user_profile'; export type SpacesService = Pick< SpacesPluginSetup['spacesService'], @@ -90,6 +92,12 @@ export interface SecurityPluginSetup { * Exposes services to access kibana roles per feature id with the GetDeprecationsContext */ privilegeDeprecationsService: PrivilegeDeprecationsService; + + /** + * Sets the flag to indicate that Kibana is running inside an Elastic Cloud deployment. This flag is supposed to be + * set by the Cloud plugin and can be only once. + */ + setIsElasticCloudDeployment: () => void; } /** @@ -188,6 +196,30 @@ export class SecurityPlugin return this.anonymousAccessStart; }; + private readonly userProfileService: UserProfileService; + private userProfileStart?: UserProfileServiceStart; + private readonly getUserProfileService = () => { + if (!this.userProfileStart) { + throw new Error(`userProfileStart is not registered!`); + } + return this.userProfileStart; + }; + + /** + * Indicates whether Kibana is running inside an Elastic Cloud deployment. Since circular plugin dependencies are + * forbidden, this flag is supposed to be set by the Cloud plugin that already depends on the Security plugin. + * @private + */ + private isElasticCloudDeployment?: boolean; + private readonly getIsElasticCloudDeployment = () => this.isElasticCloudDeployment === true; + private readonly setIsElasticCloudDeployment = () => { + if (this.isElasticCloudDeployment !== undefined) { + throw new Error(`The Elastic Cloud deployment flag has been set already!`); + } + + this.isElasticCloudDeployment = true; + }; + constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -205,6 +237,9 @@ export class SecurityPlugin this.initializerContext.logger.get('anonymous-access'), this.getConfig ); + this.userProfileService = new UserProfileService( + this.initializerContext.logger.get('user-profile') + ); } public setup( @@ -311,6 +346,7 @@ export class SecurityPlugin getFeatureUsageService: this.getFeatureUsageService, getAuthenticationService: this.getAuthentication, getAnonymousAccessService: this.getAnonymousAccess, + getUserProfileService: this.getUserProfileService, }); return Object.freeze({ @@ -333,6 +369,7 @@ export class SecurityPlugin license, logger: this.logger.get('deprecations'), }), + setIsElasticCloudDeployment: this.setIsElasticCloudDeployment, }); } @@ -357,17 +394,21 @@ export class SecurityPlugin }); this.session = session; + this.userProfileStart = this.userProfileService.start({ clusterClient }); + const config = this.getConfig(); this.authenticationStart = this.authenticationService.start({ audit: this.auditSetup!, clusterClient, config, featureUsageService: this.featureUsageServiceStart, + userProfileService: this.userProfileStart, http: core.http, loggers: this.initializerContext.logger, session, applicationName: this.authorizationSetup!.applicationName, kibanaFeatures: features.getKibanaFeatures(), + isElasticCloudDeployment: this.getIsElasticCloudDeployment, }); this.authorizationService.start({ diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index 5241c10669dbd..1e5e524796218 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -23,6 +23,7 @@ import { authorizationMock } from '../authorization/index.mock'; import { ConfigSchema, createConfig } from '../config'; import { sessionMock } from '../session_management/session.mock'; import type { SecurityRequestHandlerContext } from '../types'; +import { userProfileServiceMock } from '../user_profile/user_profile_service.mock'; export const routeDefinitionParamsMock = { create: (rawConfig: Record = {}) => { @@ -46,6 +47,7 @@ export const routeDefinitionParamsMock = { getSession: jest.fn().mockReturnValue(sessionMock.create()), getAuthenticationService: jest.fn().mockReturnValue(authenticationServiceMock.createStart()), getAnonymousAccessService: jest.fn(), + getUserProfileService: jest.fn().mockReturnValue(userProfileServiceMock.createStart()), } as unknown as DeeplyMockedKeys; }, }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index afb799cf73150..03ca6cff39d1a 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -19,6 +19,7 @@ import type { ConfigType } from '../config'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; import type { Session } from '../session_management'; import type { SecurityRouter } from '../types'; +import type { UserProfileServiceStart } from '../user_profile'; import { defineAnonymousAccessRoutes } from './anonymous_access'; import { defineApiKeysRoutes } from './api_keys'; import { defineAuthenticationRoutes } from './authentication'; @@ -28,6 +29,7 @@ import { defineIndicesRoutes } from './indices'; import { defineRoleMappingRoutes } from './role_mapping'; import { defineSecurityCheckupGetStateRoutes } from './security_checkup'; import { defineSessionManagementRoutes } from './session_management'; +import { defineUserProfileRoutes } from './user_profile'; import { defineUsersRoutes } from './users'; import { defineViewRoutes } from './views'; @@ -47,6 +49,7 @@ export interface RouteDefinitionParams { getFeatures: () => Promise; getFeatureUsageService: () => SecurityFeatureUsageServiceStart; getAuthenticationService: () => InternalAuthenticationServiceStart; + getUserProfileService: () => UserProfileServiceStart; getAnonymousAccessService: () => AnonymousAccessServiceStart; } @@ -57,6 +60,7 @@ export function defineRoutes(params: RouteDefinitionParams) { defineApiKeysRoutes(params); defineIndicesRoutes(params); defineUsersRoutes(params); + defineUserProfileRoutes(params); defineRoleMappingRoutes(params); defineViewRoutes(params); defineDeprecationsRoutes(params); diff --git a/x-pack/plugins/security/server/routes/user_profile/get.ts b/x-pack/plugins/security/server/routes/user_profile/get.ts new file mode 100644 index 0000000000000..8e62da27e050e --- /dev/null +++ b/x-pack/plugins/security/server/routes/user_profile/get.ts @@ -0,0 +1,55 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '..'; +import type { AuthenticatedUserProfile } from '../../../common'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { getPrintableSessionId } from '../../session_management'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +export function defineGetUserProfileRoute({ + router, + getSession, + getUserProfileService, + logger, +}: RouteDefinitionParams) { + router.get( + { + path: '/internal/security/user_profile', + validate: { + query: schema.object({ data: schema.maybe(schema.string()) }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const session = await getSession().get(request); + if (!session) { + return response.notFound(); + } + + if (!session.userProfileId) { + logger.warn( + `User profile missing from current session. (sid: ${getPrintableSessionId(session.sid)})` + ); + return response.notFound(); + } + + const userProfileService = getUserProfileService(); + try { + const profile = await userProfileService.get(session.userProfileId, request.query.data); + const body: AuthenticatedUserProfile = { + ...profile, + user: { ...profile.user, authentication_provider: session.provider }, + }; + return response.ok({ body }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/user_profile/index.ts b/x-pack/plugins/security/server/routes/user_profile/index.ts new file mode 100644 index 0000000000000..6d526d2ce6a75 --- /dev/null +++ b/x-pack/plugins/security/server/routes/user_profile/index.ts @@ -0,0 +1,15 @@ +/* + * 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 type { RouteDefinitionParams } from '..'; +import { defineGetUserProfileRoute } from './get'; +import { defineUpdateUserProfileDataRoute } from './update'; + +export function defineUserProfileRoutes(params: RouteDefinitionParams) { + defineUpdateUserProfileDataRoute(params); + defineGetUserProfileRoute(params); +} diff --git a/x-pack/plugins/security/server/routes/user_profile/update.test.ts b/x-pack/plugins/security/server/routes/user_profile/update.test.ts new file mode 100644 index 0000000000000..785b644a3fa12 --- /dev/null +++ b/x-pack/plugins/security/server/routes/user_profile/update.test.ts @@ -0,0 +1,150 @@ +/* + * 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 type { ObjectType } from '@kbn/config-schema'; +import type { RequestHandler, RouteConfig } from '@kbn/core/server'; +import { kibanaResponseFactory } from '@kbn/core/server'; +import { httpServerMock } from '@kbn/core/server/mocks'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; + +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import type { InternalAuthenticationServiceStart } from '../../authentication'; +import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; +import type { Session } from '../../session_management'; +import { sessionMock } from '../../session_management/session.mock'; +import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types'; +import type { UserProfileServiceStart } from '../../user_profile'; +import { userProfileServiceMock } from '../../user_profile/user_profile_service.mock'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { defineUpdateUserProfileDataRoute } from './update'; + +function getMockContext() { + return { + licensing: { + license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, + }, + } as unknown as SecurityRequestHandlerContext; +} + +describe('Update profile routes', () => { + let router: jest.Mocked; + let session: jest.Mocked>; + let userProfileService: jest.Mocked; + let authc: DeeplyMockedKeys; + beforeEach(() => { + const routeParamsMock = routeDefinitionParamsMock.create(); + router = routeParamsMock.router; + + session = sessionMock.create(); + routeParamsMock.getSession.mockReturnValue(session); + + userProfileService = userProfileServiceMock.createStart(); + routeParamsMock.getUserProfileService.mockReturnValue(userProfileService); + + authc = authenticationServiceMock.createStart(); + routeParamsMock.getAuthenticationService.mockReturnValue(authc); + + defineUpdateUserProfileDataRoute(routeParamsMock); + }); + + describe('update profile data', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + beforeEach(() => { + const [updateRouteConfig, updateRouteHandler] = router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/user_profile/_data' + )!; + + routeConfig = updateRouteConfig; + routeHandler = updateRouteHandler; + }); + + it('correctly defines route.', () => { + const bodySchema = (routeConfig.validate as any).body as ObjectType; + expect(() => bodySchema.validate(0)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [object] but got [number]"` + ); + expect(() => bodySchema.validate('avatar')).toThrowErrorMatchingInlineSnapshot( + `"could not parse record value from json input"` + ); + expect(() => bodySchema.validate(true)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [object] but got [boolean]"` + ); + expect(() => bodySchema.validate(null)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [object] but got [null]"` + ); + expect(() => bodySchema.validate(undefined)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [object] but got [undefined]"` + ); + + expect(bodySchema.validate({})).toEqual({}); + expect( + bodySchema.validate({ title: 'some-title', content: { deepProperty: { type: 'basic' } } }) + ).toEqual({ title: 'some-title', content: { deepProperty: { type: 'basic' } } }); + }); + + it('fails if session is not found.', async () => { + session.get.mockResolvedValue(null); + + await expect( + routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest({ body: {} }), + kibanaResponseFactory + ) + ).resolves.toEqual(expect.objectContaining({ status: 404 })); + + expect(userProfileService.update).not.toHaveBeenCalled(); + }); + + it('fails if session does not have profile ID.', async () => { + session.get.mockResolvedValue(sessionMock.createValue({ userProfileId: undefined })); + + await expect( + routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest({ body: {} }), + kibanaResponseFactory + ) + ).resolves.toEqual(expect.objectContaining({ status: 404 })); + + expect(userProfileService.update).not.toHaveBeenCalled(); + }); + + it('fails for Elastic Cloud users.', async () => { + session.get.mockResolvedValue(sessionMock.createValue()); + authc.getCurrentUser.mockReturnValue(mockAuthenticatedUser({ elastic_cloud_user: true })); + + await expect( + routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest({ body: {} }), + kibanaResponseFactory + ) + ).resolves.toEqual(expect.objectContaining({ status: 403 })); + + expect(userProfileService.update).not.toHaveBeenCalled(); + }); + + it('updates profile.', async () => { + session.get.mockResolvedValue(sessionMock.createValue({ userProfileId: 'u_some_id' })); + authc.getCurrentUser.mockReturnValue(mockAuthenticatedUser()); + + await expect( + routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest({ body: { some: 'property' } }), + kibanaResponseFactory + ) + ).resolves.toEqual(expect.objectContaining({ status: 200, payload: undefined })); + + expect(userProfileService.update).toBeCalledTimes(1); + expect(userProfileService.update).toBeCalledWith('u_some_id', { some: 'property' }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/user_profile/update.ts b/x-pack/plugins/security/server/routes/user_profile/update.ts new file mode 100644 index 0000000000000..d4c030d0e0306 --- /dev/null +++ b/x-pack/plugins/security/server/routes/user_profile/update.ts @@ -0,0 +1,62 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '..'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { getPrintableSessionId } from '../../session_management'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +export function defineUpdateUserProfileDataRoute({ + router, + getSession, + getUserProfileService, + logger, + getAuthenticationService, +}: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/user_profile/_data', + validate: { + body: schema.recordOf(schema.string(), schema.any()), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const session = await getSession().get(request); + if (!session) { + logger.warn('User profile requested without valid session.'); + return response.notFound(); + } + + if (!session.userProfileId) { + logger.warn( + `User profile missing from current session. (sid: ${getPrintableSessionId(session.sid)})` + ); + return response.notFound(); + } + + const currentUser = getAuthenticationService().getCurrentUser(request); + if (currentUser?.elastic_cloud_user) { + logger.warn( + `Elastic Cloud SSO users aren't allowed to update profiles in Kibana. (sid: ${getPrintableSessionId( + session.sid + )})` + ); + return response.forbidden(); + } + + const userProfileService = getUserProfileService(); + try { + await userProfileService.update(session.userProfileId, request.body); + return response.ok(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/session_management/index.ts b/x-pack/plugins/security/server/session_management/index.ts index 09787ed419854..5145efe587661 100644 --- a/x-pack/plugins/security/server/session_management/index.ts +++ b/x-pack/plugins/security/server/session_management/index.ts @@ -6,6 +6,6 @@ */ export type { SessionValue } from './session'; -export { Session } from './session'; +export { Session, getPrintableSessionId } from './session'; export type { SessionManagementServiceStart } from './session_management_service'; export { SessionManagementService } from './session_management_service'; diff --git a/x-pack/plugins/security/server/session_management/session.mock.ts b/x-pack/plugins/security/server/session_management/session.mock.ts index 65ae43e5fa705..3941e38a98ca1 100644 --- a/x-pack/plugins/security/server/session_management/session.mock.ts +++ b/x-pack/plugins/security/server/session_management/session.mock.ts @@ -24,6 +24,7 @@ export const sessionMock = { createValue: (sessionValue: Partial = {}): SessionValue => ({ sid: 'some-long-sid', username: mockAuthenticatedUser().username, + userProfileId: 'uid', provider: { type: 'basic', name: 'basic1' }, idleTimeoutExpiration: null, lifespanExpiration: null, diff --git a/x-pack/plugins/security/server/session_management/session.test.ts b/x-pack/plugins/security/server/session_management/session.test.ts index 11fbd168780d1..dfe9ca8d5747d 100644 --- a/x-pack/plugins/security/server/session_management/session.test.ts +++ b/x-pack/plugins/security/server/session_management/session.test.ts @@ -15,7 +15,7 @@ import { mockAuthenticatedUser } from '../../common/model/authenticated_user.moc import { ConfigSchema, createConfig } from '../config'; import { sessionCookieMock, sessionIndexMock, sessionMock } from './index.mock'; import type { SessionValueContentToEncrypt } from './session'; -import { Session } from './session'; +import { getPrintableSessionId, Session } from './session'; import type { SessionCookie } from './session_cookie'; import type { SessionIndex } from './session_index'; @@ -80,7 +80,10 @@ describe('Session', () => { mockSessionCookie.get.mockResolvedValue(null); mockSessionIndex.get.mockResolvedValue( sessionIndexMock.createValue({ - content: await encryptContent({ username: 'some-user', state: 'some-state' }, mockAAD), + content: await encryptContent( + { username: 'some-user', state: 'some-state', userProfileId: 'uid' }, + mockAAD + ), }) ); @@ -97,7 +100,10 @@ describe('Session', () => { ); mockSessionIndex.get.mockResolvedValue( sessionIndexMock.createValue({ - content: await encryptContent({ username: 'some-user', state: 'some-state' }, mockAAD), + content: await encryptContent( + { username: 'some-user', state: 'some-state', userProfileId: 'uid' }, + mockAAD + ), }) ); @@ -116,7 +122,10 @@ describe('Session', () => { ); mockSessionIndex.get.mockResolvedValue( sessionIndexMock.createValue({ - content: await encryptContent({ username: 'some-user', state: 'some-state' }, mockAAD), + content: await encryptContent( + { username: 'some-user', state: 'some-state', userProfileId: 'uid' }, + mockAAD + ), }) ); @@ -164,7 +173,10 @@ describe('Session', () => { ); mockSessionIndex.get.mockResolvedValue( sessionIndexMock.createValue({ - content: await encryptContent({ username: 'some-user', state: 'some-state' }, mockAAD), + content: await encryptContent( + { username: 'some-user', state: 'some-state', userProfileId: 'uid' }, + mockAAD + ), }) ); @@ -185,7 +197,10 @@ describe('Session', () => { const mockSessionIndexValue = sessionIndexMock.createValue({ idleTimeoutExpiration: now - 1, lifespanExpiration: now + 1, - content: await encryptContent({ username: 'some-user', state: 'some-state' }, mockAAD), + content: await encryptContent( + { username: 'some-user', state: 'some-state', userProfileId: 'uid' }, + mockAAD + ), }); mockSessionIndex.get.mockResolvedValue(mockSessionIndexValue); @@ -197,6 +212,35 @@ describe('Session', () => { sid: 'some-long-sid', state: 'some-state', username: 'some-user', + userProfileId: 'uid', + }); + expect(mockSessionCookie.clear).not.toHaveBeenCalled(); + expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); + }); + + it('returns session value with decrypted content if optional fields are missing', async () => { + mockSessionCookie.get.mockResolvedValue( + sessionCookieMock.createValue({ + aad: mockAAD, + idleTimeoutExpiration: now + 1, + lifespanExpiration: now + 1, + }) + ); + + const mockSessionIndexValue = sessionIndexMock.createValue({ + idleTimeoutExpiration: now - 1, + lifespanExpiration: now + 1, + content: await encryptContent({ state: 'some-state' }, mockAAD), + }); + mockSessionIndex.get.mockResolvedValue(mockSessionIndexValue); + + await expect(session.get(httpServerMock.createKibanaRequest())).resolves.toEqual({ + idleTimeoutExpiration: now + 1, + lifespanExpiration: now + 1, + metadata: { index: mockSessionIndexValue }, + provider: { name: 'basic1', type: 'basic' }, + sid: 'some-long-sid', + state: 'some-state', }); expect(mockSessionCookie.clear).not.toHaveBeenCalled(); expect(mockSessionIndex.invalidate).not.toHaveBeenCalled(); @@ -219,12 +263,14 @@ describe('Session', () => { await expect( session.create(mockRequest, { username: mockAuthenticatedUser().username, + userProfileId: 'uid', provider: { type: 'basic', name: 'basic1' }, state: 'some-state', }) ).resolves.toEqual({ sid: mockSID, username: 'user', + userProfileId: 'uid', state: 'some-state', provider: { name: 'basic1', type: 'basic' }, idleTimeoutExpiration: now + 123, @@ -237,7 +283,7 @@ describe('Session', () => { expect(mockSessionIndex.create).toHaveBeenCalledWith({ sid: mockSID, content: - 'AwABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9PgQAAQIDBAUGBwgJCpgMitlj6jACf9fYYa66WkuUpJsdgbWevIEfo6mN827f0lGcKDNPzN+vDMMPFetOkRITDI+NMz7e3JcMofnDboRnvg==', + 'AwABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9PgQAAQIDBAUGBwgJChvXfqiKj6k6TtyXr4HA5s2UpJsdgbWevIEfo6mN827f0lGcKDNPzN+vDMUIEe17v19POcGbFTfn094k4vjPVvo4sKPZMbNZmsQnCuqRNKIx4DZwVTlsKNizEUKP', provider: { name: 'basic1', type: 'basic' }, usernameHash: '8ac76453d769d4fd14b3f41ad4933f9bd64321972cd002de9b847e117435b08b', idleTimeoutExpiration: now + 123, @@ -253,6 +299,53 @@ describe('Session', () => { lifespanExpiration: now + 456, }); }); + + it('creates session value if optional fields are missing', async () => { + const mockSID = Buffer.from([1, ...Array(31).keys()]).toString('base64'); + const mockAAD = Buffer.from([2, ...Array(31).keys()]).toString('base64'); + + const mockSessionIndexValue = sessionIndexMock.createValue({ + sid: mockSID, + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 456, + }); + mockSessionIndex.create.mockResolvedValue(mockSessionIndexValue); + + const mockRequest = httpServerMock.createKibanaRequest(); + await expect( + session.create(mockRequest, { + provider: { type: 'basic', name: 'basic1' }, + state: 'some-state', + }) + ).resolves.toEqual({ + sid: mockSID, + state: 'some-state', + provider: { name: 'basic1', type: 'basic' }, + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 456, + metadata: { index: mockSessionIndexValue }, + }); + + // Properly creates session index value. + expect(mockSessionIndex.create).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.create).toHaveBeenCalledWith({ + sid: mockSID, + content: + 'AwABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9PgQAAQIDBAUGBwgJCnCZRj98P6cRfxXeABhK52uUpJsdh7Kauooi7PaN8yfsnUHCPjVympavDM1Z', + provider: { name: 'basic1', type: 'basic' }, + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 456, + }); + + // Properly creates session cookie value. + expect(mockSessionCookie.set).toHaveBeenCalledTimes(1); + expect(mockSessionCookie.set).toHaveBeenCalledWith(mockRequest, { + sid: mockSID, + aad: mockAAD, + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 456, + }); + }); }); describe('#update', () => { @@ -263,7 +356,10 @@ describe('Session', () => { // To make sure we aren't even calling this method. mockSessionIndex.update.mockResolvedValue( sessionIndexMock.createValue({ - content: await encryptContent({ username: 'some-user', state: 'some-state' }, mockAAD), + content: await encryptContent( + { username: 'some-user', state: 'some-state', userProfileId: 'uid' }, + mockAAD + ), }) ); @@ -306,6 +402,7 @@ describe('Session', () => { mockRequest, sessionMock.createValue({ username: 'new-user', + userProfileId: 'new-uid', state: 'new-state', idleTimeoutExpiration: now + 1, lifespanExpiration: now + 1, @@ -314,6 +411,7 @@ describe('Session', () => { ).resolves.toEqual({ sid: 'some-long-sid', username: 'new-user', + userProfileId: 'new-uid', state: 'new-state', provider: { name: 'basic1', type: 'basic' }, idleTimeoutExpiration: now + 123, @@ -326,7 +424,7 @@ describe('Session', () => { expect(mockSessionIndex.update).toHaveBeenCalledWith({ sid: 'some-long-sid', content: - 'AQABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9PgIAAQIDBAUGBwgJCt8yPPMsaNAxn7qtLtc57UN967e9FpjmJgEIipe6nD20F47TtNIZnAuzd75zc8TNWvPMgRTzpHnYz7cT9m5ouv2V8TZ+ow==', + 'AQABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9PgIAAQIDBAUGBwgJClC2bGDkxgH3vSXI2+oux3R967e9FpjmJgEIipe6nD20F47TtNIZnAuzd75zc8TLXffKtDq++EyWxJsAhz8mh6ueyGMu9LSLuawgiBy7y2ULLjoCPZwslb1GPrBe+9+BiE9Jow==', provider: { name: 'basic1', type: 'basic' }, usernameHash: '35133597af273830c3f139c72501e676338f28a39dca8ff62d5c2b8bfba75f69', idleTimeoutExpiration: now + 123, @@ -345,6 +443,64 @@ describe('Session', () => { }); }); + it('updates session value if optional fields are missing', async () => { + mockSessionCookie.get.mockResolvedValue( + sessionCookieMock.createValue({ + aad: mockAAD, + idleTimeoutExpiration: now + 1, + lifespanExpiration: now + 1, + }) + ); + + const mockSessionIndexValue = sessionIndexMock.createValue({ + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 1, + metadata: { primaryTerm: 2, sequenceNumber: 2 }, + }); + mockSessionIndex.update.mockResolvedValue(mockSessionIndexValue); + + const mockRequest = httpServerMock.createKibanaRequest(); + await expect( + session.update( + mockRequest, + sessionMock.createValue({ + username: undefined, + userProfileId: undefined, + idleTimeoutExpiration: now + 1, + lifespanExpiration: now + 1, + }) + ) + ).resolves.toEqual({ + sid: 'some-long-sid', + provider: { name: 'basic1', type: 'basic' }, + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 1, + metadata: { index: mockSessionIndexValue }, + }); + + // Properly updates session index value. + expect(mockSessionIndex.update).toHaveBeenCalledTimes(1); + expect(mockSessionIndex.update).toHaveBeenCalledWith({ + sid: 'some-long-sid', + content: + 'AQABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9PgIAAQIDBAUGBwgJCvi9HQSbzhgXyhSge7K3rrF965a9', + provider: { name: 'basic1', type: 'basic' }, + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 1, + metadata: { primaryTerm: 1, sequenceNumber: 1 }, + }); + + // Properly updates session cookie value. + expect(mockSessionCookie.set).toHaveBeenCalledTimes(1); + expect(mockSessionCookie.set).toHaveBeenCalledWith(mockRequest, { + sid: 'some-long-sid', + aad: mockAAD, + path: '/mock-base-path', + idleTimeoutExpiration: now + 123, + lifespanExpiration: now + 1, + }); + }); + it('properly extends session expiration if idle timeout is defined.', async () => { mockSessionCookie.get.mockResolvedValue( sessionCookieMock.createValue({ aad: mockAAD, idleTimeoutExpiration: now + 1 }) @@ -882,4 +1038,8 @@ describe('Session', () => { }); }); }); + + it('#getPrintableSessionId', async () => { + expect(getPrintableSessionId('1234567890abcdefghijklmno')).toBe('fghijklmno'); + }); }); diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index febeb6db55f76..8f6181ae3bee3 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -13,7 +13,7 @@ import { promisify } from 'util'; import type { KibanaRequest, Logger } from '@kbn/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { AuthenticationProvider } from '../../common/model'; +import type { AuthenticationProvider } from '../../common'; import type { ConfigType } from '../config'; import type { SessionCookie } from './session_cookie'; import type { SessionIndex, SessionIndexValue } from './session_index'; @@ -56,6 +56,12 @@ export interface SessionValue { */ state: unknown; + /** + * Unique identifier of the user profile, if any. Not all users that have session will have an associated user + * profile, e.g. anonymous users won't have it. + */ + userProfileId?: string; + /** * Indicates whether user acknowledged access agreement or not. */ @@ -76,6 +82,7 @@ export interface SessionOptions { export interface SessionValueContentToEncrypt { username?: string; + userProfileId?: string; state: unknown; } @@ -100,6 +107,15 @@ export type InvalidateSessionsFilter = const SID_BYTE_LENGTH = 32; const AAD_BYTE_LENGTH = 32; +/** + * Returns last 10 characters of the session identifier. Referring to the specific session by its identifier is useful + * for logging and debugging purposes, but we cannot include full session ID in logs because of the security reasons. + * @param sid Full user session id + */ +export function getPrintableSessionId(sid: string) { + return sid.slice(-10); +} + export class Session { /** * Used to encrypt and decrypt portion of the session value using configured encryption key. @@ -173,7 +189,7 @@ export class Session { return { ...Session.sessionIndexValueToSessionValue(sessionIndexValue, decryptedContent), - // Unlike session index, session cookie contains the most up to date idle timeout expiration. + // Unlike session index, session cookie contains the most up-to-date idle timeout expiration. idleTimeoutExpiration: sessionCookieValue.idleTimeoutExpiration, }; } @@ -198,7 +214,7 @@ export class Session { sessionLogger.debug('Creating a new session.'); const sessionExpirationInfo = this.calculateExpiry(sessionValue.provider); - const { username, state, ...publicSessionValue } = sessionValue; + const { username, userProfileId, state, ...publicSessionValue } = sessionValue; // First try to store session in the index and only then in the cookie to make sure cookie is // only updated if server side session is created successfully. @@ -207,14 +223,18 @@ export class Session { ...sessionExpirationInfo, sid, usernameHash: username && Session.getUsernameHash(username), - content: await this.crypto.encrypt(JSON.stringify({ username, state }), aad), + content: await this.crypto.encrypt(JSON.stringify({ username, userProfileId, state }), aad), }); await this.options.sessionCookie.set(request, { ...sessionExpirationInfo, sid, aad }); sessionLogger.debug('Successfully created a new session.'); - return Session.sessionIndexValueToSessionValue(sessionIndexValue, { username, state }); + return Session.sessionIndexValueToSessionValue(sessionIndexValue, { + username, + userProfileId, + state, + }); } /** @@ -234,7 +254,7 @@ export class Session { sessionValue.provider, sessionCookieValue.lifespanExpiration ); - const { username, state, metadata, ...publicSessionInfo } = sessionValue; + const { username, userProfileId, state, metadata, ...publicSessionInfo } = sessionValue; // First try to store session in the index and only then in the cookie to make sure cookie is // only updated if server side session is created successfully. @@ -244,7 +264,7 @@ export class Session { ...sessionExpirationInfo, usernameHash: username && Session.getUsernameHash(username), content: await this.crypto.encrypt( - JSON.stringify({ username, state }), + JSON.stringify({ username, userProfileId, state }), sessionCookieValue.aad ), }); @@ -264,7 +284,11 @@ export class Session { sessionLogger.debug('Successfully updated existing session.'); - return Session.sessionIndexValueToSessionValue(sessionIndexValue, { username, state }); + return Session.sessionIndexValueToSessionValue(sessionIndexValue, { + username, + userProfileId, + state, + }); } /** @@ -446,11 +470,17 @@ export class Session { */ private static sessionIndexValueToSessionValue( sessionIndexValue: Readonly, - { username, state }: SessionValueContentToEncrypt + { username, userProfileId, state }: SessionValueContentToEncrypt ): Readonly { // Extract values that are specific to session index value. const { usernameHash, content, ...publicSessionValue } = sessionIndexValue; - return { ...publicSessionValue, username, state, metadata: { index: sessionIndexValue } }; + return { + ...publicSessionValue, + username, + userProfileId, + state, + metadata: { index: sessionIndexValue }, + }; } /** @@ -458,7 +488,7 @@ export class Session { * @param [sid] Session ID to create logger for. */ private getLoggerForSID(sid?: string) { - return this.options.logger.get(sid?.slice(-10) ?? 'no_session'); + return this.options.logger.get(sid ? getPrintableSessionId(sid) : 'no_session'); } /** diff --git a/x-pack/plugins/security/server/user_profile/index.ts b/x-pack/plugins/security/server/user_profile/index.ts new file mode 100644 index 0000000000000..dd058107ffa60 --- /dev/null +++ b/x-pack/plugins/security/server/user_profile/index.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export { UserProfileService } from './user_profile_service'; +export type { + UserProfileServiceStart, + UserProfileServiceStartParams, +} from './user_profile_service'; +export type { UserProfileGrant } from './user_profile_grant'; diff --git a/x-pack/plugins/security/server/user_profile/user_profile_grant.ts b/x-pack/plugins/security/server/user_profile/user_profile_grant.ts new file mode 100644 index 0000000000000..b12a46b00174b --- /dev/null +++ b/x-pack/plugins/security/server/user_profile/user_profile_grant.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/** + * Represents a union of all possible user profile grant types. + */ +export type UserProfileGrant = PasswordUserProfileGrant | AccessTokenUserProfileGrant; + +/** + * The user profile grant represented by the username and password. + */ +export interface PasswordUserProfileGrant { + readonly type: 'password'; + readonly username: string; + readonly password: string; +} + +/** + * The user profile grant represented by the access token. + */ +export interface AccessTokenUserProfileGrant { + readonly type: 'accessToken'; + readonly accessToken: string; +} diff --git a/x-pack/plugins/security/server/user_profile/user_profile_service.mock.ts b/x-pack/plugins/security/server/user_profile/user_profile_service.mock.ts new file mode 100644 index 0000000000000..e6fd72c15f110 --- /dev/null +++ b/x-pack/plugins/security/server/user_profile/user_profile_service.mock.ts @@ -0,0 +1,17 @@ +/* + * 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 type { UserProfileServiceStart } from '.'; +import { userProfileMock } from '../../common/model/user_profile.mock'; + +export const userProfileServiceMock = { + createStart: (): jest.Mocked => ({ + activate: jest.fn().mockReturnValue(userProfileMock.create()), + get: jest.fn(), + update: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts b/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts new file mode 100644 index 0000000000000..a2646c3e957b0 --- /dev/null +++ b/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts @@ -0,0 +1,412 @@ +/* + * 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 { errors } from '@elastic/elasticsearch'; + +import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { nextTick } from '@kbn/test-jest-helpers'; + +import { userProfileMock } from '../../common/model/user_profile.mock'; +import { securityMock } from '../mocks'; +import { UserProfileService } from './user_profile_service'; + +const logger = loggingSystemMock.createLogger(); +const userProfileService = new UserProfileService(logger); + +describe('UserProfileService', () => { + let mockStartParams: { + clusterClient: ReturnType; + }; + + beforeEach(() => { + mockStartParams = { + clusterClient: elasticsearchServiceMock.createClusterClient(), + }; + + const userProfile = userProfileMock.create({ + uid: 'UID', + data: { + kibana: { + avatar: 'fun.gif', + }, + other_app: { + secret: 'data', + }, + }, + }); + mockStartParams.clusterClient.asInternalUser.transport.request.mockResolvedValue({ + [userProfile.uid]: userProfile, + }); + }); + + afterEach(() => { + logger.error.mockClear(); + }); + + it('should expose correct start contract', () => { + const startContract = userProfileService.start(mockStartParams); + expect(startContract).toMatchInlineSnapshot(` + Object { + "activate": [Function], + "get": [Function], + "update": [Function], + } + `); + }); + + describe('#get', () => { + it('should get user profile', async () => { + const startContract = userProfileService.start(mockStartParams); + await expect(startContract.get('UID')).resolves.toMatchInlineSnapshot(` + Object { + "data": Object { + "avatar": "fun.gif", + }, + "enabled": true, + "uid": "UID", + "user": Object { + "active": true, + "authentication_provider": Object { + "name": "basic1", + "type": "basic", + }, + "authentication_realm": Object { + "name": "native1", + "type": "native", + }, + "authentication_type": "realm", + "elastic_cloud_user": false, + "email": "email", + "enabled": true, + "full_name": "full name", + "lookup_realm": Object { + "name": "native1", + "type": "native", + }, + "metadata": Object { + "_reserved": false, + }, + "roles": Array [], + "username": "some-username", + }, + } + `); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: '_security/profile/UID', + }); + }); + + it('should handle errors when get user profile fails', async () => { + mockStartParams.clusterClient.asInternalUser.transport.request.mockRejectedValue( + new Error('Fail') + ); + const startContract = userProfileService.start(mockStartParams); + await expect(startContract.get('UID')).rejects.toMatchInlineSnapshot(`[Error: Fail]`); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should get user profile and application data scoped to Kibana', async () => { + const startContract = userProfileService.start(mockStartParams); + await expect(startContract.get('UID', '*')).resolves.toMatchInlineSnapshot(` + Object { + "data": Object { + "avatar": "fun.gif", + }, + "enabled": true, + "uid": "UID", + "user": Object { + "active": true, + "authentication_provider": Object { + "name": "basic1", + "type": "basic", + }, + "authentication_realm": Object { + "name": "native1", + "type": "native", + }, + "authentication_type": "realm", + "elastic_cloud_user": false, + "email": "email", + "enabled": true, + "full_name": "full name", + "lookup_realm": Object { + "name": "native1", + "type": "native", + }, + "metadata": Object { + "_reserved": false, + }, + "roles": Array [], + "username": "some-username", + }, + } + `); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'GET', + path: '_security/profile/UID?data=kibana.*', + }); + }); + }); + + describe('#update', () => { + it('should update application data scoped to Kibana', async () => { + const startContract = userProfileService.start(mockStartParams); + await startContract.update('UID', { + avatar: 'boring.png', + }); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + body: { + data: { + kibana: { + avatar: 'boring.png', + }, + }, + }, + method: 'POST', + path: '_security/profile/UID/_data', + }); + }); + + it('should handle errors when update user profile fails', async () => { + mockStartParams.clusterClient.asInternalUser.transport.request.mockRejectedValue( + new Error('Fail') + ); + const startContract = userProfileService.start(mockStartParams); + await expect( + startContract.update('UID', { + avatar: 'boring.png', + }) + ).rejects.toMatchInlineSnapshot(`[Error: Fail]`); + expect(logger.error).toHaveBeenCalled(); + }); + }); + + describe('#activate', () => { + beforeEach(() => { + mockStartParams.clusterClient.asInternalUser.transport.request.mockResolvedValue( + userProfileMock.create() + ); + }); + + it('should activate user profile with password grant', async () => { + const startContract = userProfileService.start(mockStartParams); + await expect( + startContract.activate({ + type: 'password', + username: 'some-username', + password: 'password', + }) + ).resolves.toMatchInlineSnapshot(` + Object { + "data": Object {}, + "enabled": true, + "uid": "some-profile-uid", + "user": Object { + "active": true, + "authentication_provider": Object { + "name": "basic1", + "type": "basic", + }, + "authentication_realm": Object { + "name": "native1", + "type": "native", + }, + "authentication_type": "realm", + "elastic_cloud_user": false, + "email": "email", + "enabled": true, + "full_name": "full name", + "lookup_realm": Object { + "name": "native1", + "type": "native", + }, + "metadata": Object { + "_reserved": false, + }, + "roles": Array [], + "username": "some-username", + }, + } + `); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledTimes( + 1 + ); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '_security/profile/_activate', + body: { grant_type: 'password', password: 'password', username: 'some-username' }, + }); + }); + + it('should activate user profile with access token grant', async () => { + const startContract = userProfileService.start(mockStartParams); + await expect(startContract.activate({ type: 'accessToken', accessToken: 'some-token' })) + .resolves.toMatchInlineSnapshot(` + Object { + "data": Object {}, + "enabled": true, + "uid": "some-profile-uid", + "user": Object { + "active": true, + "authentication_provider": Object { + "name": "basic1", + "type": "basic", + }, + "authentication_realm": Object { + "name": "native1", + "type": "native", + }, + "authentication_type": "realm", + "elastic_cloud_user": false, + "email": "email", + "enabled": true, + "full_name": "full name", + "lookup_realm": Object { + "name": "native1", + "type": "native", + }, + "metadata": Object { + "_reserved": false, + }, + "roles": Array [], + "username": "some-username", + }, + } + `); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledTimes( + 1 + ); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '_security/profile/_activate', + body: { grant_type: 'access_token', access_token: 'some-token' }, + }); + }); + + it('fails if activation fails with non-409 error', async () => { + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 500, body: 'some message' }) + ); + mockStartParams.clusterClient.asInternalUser.transport.request.mockRejectedValue( + failureReason + ); + + const startContract = userProfileService.start(mockStartParams); + await expect( + startContract.activate({ type: 'accessToken', accessToken: 'some-token' }) + ).rejects.toBe(failureReason); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledTimes( + 1 + ); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '_security/profile/_activate', + body: { grant_type: 'access_token', access_token: 'some-token' }, + }); + }); + + it('retries activation if initially fails with 409 error', async () => { + jest.useFakeTimers(); + + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 409, body: 'some message' }) + ); + mockStartParams.clusterClient.asInternalUser.transport.request + .mockRejectedValueOnce(failureReason) + .mockResolvedValueOnce(userProfileMock.create()); + + const startContract = userProfileService.start(mockStartParams); + const activatePromise = startContract.activate({ + type: 'accessToken', + accessToken: 'some-token', + }); + await nextTick(); + jest.runAllTimers(); + + await expect(activatePromise).resolves.toMatchInlineSnapshot(` + Object { + "data": Object {}, + "enabled": true, + "uid": "some-profile-uid", + "user": Object { + "active": true, + "authentication_provider": Object { + "name": "basic1", + "type": "basic", + }, + "authentication_realm": Object { + "name": "native1", + "type": "native", + }, + "authentication_type": "realm", + "elastic_cloud_user": false, + "email": "email", + "enabled": true, + "full_name": "full name", + "lookup_realm": Object { + "name": "native1", + "type": "native", + }, + "metadata": Object { + "_reserved": false, + }, + "roles": Array [], + "username": "some-username", + }, + } + `); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledTimes( + 2 + ); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '_security/profile/_activate', + body: { grant_type: 'access_token', access_token: 'some-token' }, + }); + }); + + it('fails if activation max retries exceeded', async () => { + jest.useFakeTimers(); + + const failureReason = new errors.ResponseError( + securityMock.createApiResponse({ statusCode: 409, body: 'some message' }) + ); + mockStartParams.clusterClient.asInternalUser.transport.request.mockRejectedValue( + failureReason + ); + + const startContract = userProfileService.start(mockStartParams); + + // Initial activation attempt. + const activatePromise = startContract.activate({ + type: 'accessToken', + accessToken: 'some-token', + }); + await nextTick(); + jest.runAllTimers(); + + // The first retry. + await nextTick(); + jest.runAllTimers(); + + // The second retry. + await nextTick(); + jest.runAllTimers(); + + await expect(activatePromise).rejects.toBe(failureReason); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledTimes( + 3 + ); + expect(mockStartParams.clusterClient.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '_security/profile/_activate', + body: { grant_type: 'access_token', access_token: 'some-token' }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/user_profile/user_profile_service.ts b/x-pack/plugins/security/server/user_profile/user_profile_service.ts new file mode 100644 index 0000000000000..fcc62f87fd953 --- /dev/null +++ b/x-pack/plugins/security/server/user_profile/user_profile_service.ts @@ -0,0 +1,154 @@ +/* + * 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 type { IClusterClient, Logger } from '@kbn/core/server'; + +import type { AuthenticationProvider, UserData, UserInfo, UserProfile } from '../../common'; +import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; +import type { UserProfileGrant } from './user_profile_grant'; + +const KIBANA_DATA_ROOT = 'kibana'; +const ACTIVATION_MAX_RETRIES = 3; +const ACTIVATION_RETRY_SCALE_DURATION_MS = 150; + +export interface UserProfileServiceStart { + /** + * Activates user profile using provided user profile grant. + * @param grant User profile grant (username/password or access token). + */ + activate(grant: UserProfileGrant): Promise; + + /** + * Retrieves a single user profile by identifier. + * @param uid User ID + * @param dataPath By default `get()` returns user information, but does not return any user data. The optional "dataPath" parameter can be used to return personal data for this user. + */ + get(uid: string, dataPath?: string): Promise>; + + /** + * Updates user preferences by identifier. + * @param uid User ID + * @param data Application data to be written (merged with existing data). + */ + update(uid: string, data: T): Promise; +} + +type GetProfileResponse = Record< + string, + { + uid: string; + user: UserInfo; + data: { + [KIBANA_DATA_ROOT]: T; + }; + access: {}; + enabled: boolean; + last_synchronized: number; + authentication_provider: AuthenticationProvider; + } +>; + +export interface UserProfileServiceStartParams { + clusterClient: IClusterClient; +} + +export class UserProfileService { + constructor(private readonly logger: Logger) {} + + start({ clusterClient }: UserProfileServiceStartParams): UserProfileServiceStart { + const { logger } = this; + + async function activate(grant: UserProfileGrant): Promise { + logger.debug(`Activating user profile via ${grant.type} grant.`); + + const activateGrant = + grant.type === 'password' + ? { grant_type: 'password', username: grant.username, password: grant.password } + : { grant_type: 'access_token', access_token: grant.accessToken }; + + // Profile activation is a multistep process that might or might not cause profile document to be created or + // updated. If Elasticsearch needs to handle multiple profile activation requests for the same user in parallel + // it can hit document version conflicts and fail (409 status code). In this case it's safe to retry activation + // request after some time. Most of the Kibana users won't be affected by this issue, but there are edge cases + // when users can be hit by the conflicts during profile activation, e.g. for PKI or Kerberos authentication when + // client certificate/ticket changes and multiple requests can trigger profile re-activation at the same time. + let activationRetriesLeft = ACTIVATION_MAX_RETRIES; + do { + try { + const response = await clusterClient.asInternalUser.transport.request({ + method: 'POST', + path: '_security/profile/_activate', + body: activateGrant, + }); + + logger.debug(`Successfully activated profile for "${response.user.username}".`); + + return response; + } catch (err) { + const detailedErrorMessage = getDetailedErrorMessage(err); + if (getErrorStatusCode(err) !== 409) { + logger.error(`Failed to activate user profile: ${detailedErrorMessage}.`); + throw err; + } + + activationRetriesLeft--; + logger.error( + `Failed to activate user profile (retries left: ${activationRetriesLeft}): ${detailedErrorMessage}.` + ); + + if (activationRetriesLeft === 0) { + throw err; + } + } + + await new Promise((resolve) => + setTimeout( + resolve, + (ACTIVATION_MAX_RETRIES - activationRetriesLeft) * ACTIVATION_RETRY_SCALE_DURATION_MS + ) + ); + } while (activationRetriesLeft > 0); + + // This should be unreachable code, unless we have a bug in retry handling logic. + throw new Error('Failed to activate user profile, max retries exceeded.'); + } + + async function get(uid: string, dataPath?: string) { + try { + const body = await clusterClient.asInternalUser.transport.request>({ + method: 'GET', + path: `_security/profile/${uid}${ + dataPath ? `?data=${KIBANA_DATA_ROOT}.${dataPath}` : '' + }`, + }); + return { ...body[uid], data: body[uid].data[KIBANA_DATA_ROOT] ?? {} }; + } catch (error) { + logger.error(`Failed to retrieve user profile [uid=${uid}]: ${error.message}`); + throw error; + } + } + + async function update(uid: string, data: T) { + try { + await clusterClient.asInternalUser.transport.request({ + method: 'POST', + path: `_security/profile/${uid}/_data`, + body: { + data: { + [KIBANA_DATA_ROOT]: data, + }, + }, + }); + } catch (error) { + logger.error(`Failed to update user profile [uid=${uid}]: ${error.message}`); + throw error; + } + } + + return { activate, get, update }; + } +} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 8e7a928cedeca..52e4d54383d2d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -22828,7 +22828,6 @@ "xpack.security.account.changePasswordTitle": "Mot de passe", "xpack.security.account.currentPasswordRequired": "Le mot de passe actuel est requis.", "xpack.security.account.noEmailMessage": "aucune adresse e-mail", - "xpack.security.account.pageTitle": "Paramètres de {strongUsername}", "xpack.security.account.passwordLengthDescription": "Le mot de passe est trop court.", "xpack.security.account.passwordsDoNotMatch": "Les mots de passe ne correspondent pas.", "xpack.security.account.usernameGroupDescription": "Vous ne pouvez pas modifier ces informations.", @@ -23240,22 +23239,6 @@ "xpack.security.management.roles.statusColumnName": "Statut", "xpack.security.management.roles.subtitle": "Appliquez les rôles aux groupes d'utilisateurs et gérez les autorisations dans toute la Suite.", "xpack.security.management.rolesTitle": "Rôles", - "xpack.security.management.users.changePasswordFlyout.confirmButton": "{isSubmitting, select, true{Modification du mot de passe…} other{Modifier le mot de passe}}", - "xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError": "Les mots de passe ne correspondent pas.", - "xpack.security.management.users.changePasswordFlyout.confirmPasswordLabel": "Confirmer le mot de passe", - "xpack.security.management.users.changePasswordFlyout.confirmSystemPasswordButton": "{isSubmitting, select, true{Modification du mot de passe…} other{Modifier le mot de passe}}", - "xpack.security.management.users.changePasswordFlyout.currentPasswordInvalidError": "Mot de passe non valide.", - "xpack.security.management.users.changePasswordFlyout.currentPasswordLabel": "Mot de passe actuel", - "xpack.security.management.users.changePasswordFlyout.currentPasswordRequiredError": "Entrez votre mot de passe actuel.", - "xpack.security.management.users.changePasswordFlyout.errorMessage": "Impossible de modifier le mot de passe", - "xpack.security.management.users.changePasswordFlyout.passwordInvalidError": "Le mot de passe doit comporter au moins 6 caractères.", - "xpack.security.management.users.changePasswordFlyout.passwordLabel": "Nouveau mot de passe", - "xpack.security.management.users.changePasswordFlyout.passwordRequiredError": "Entrez un nouveau mot de passe.", - "xpack.security.management.users.changePasswordFlyout.successMessage": "Mot de passe modifié pour \"{username}\".", - "xpack.security.management.users.changePasswordFlyout.systemUserDescription": "Une fois modifié, vous devez mettre à jour manuellement votre fichier config avec le nouveau mot de passe et redémarrer Kibana.", - "xpack.security.management.users.changePasswordFlyout.systemUserTitle": "C'est extrêmement important !", - "xpack.security.management.users.changePasswordFlyout.systemUserWarning": "La modification de ce mot de passe empêchera Kibana de communiquer avec Elasticsearch.", - "xpack.security.management.users.changePasswordFlyout.title": "Modifier le mot de passe", "xpack.security.management.users.changePasswordFlyout.userLabel": "Utilisateur", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "Annuler", "xpack.security.management.users.confirmDelete.cannotUndoWarning": "Vous ne pouvez pas récupérer des utilisateurs supprimés.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 59d0f4d727ca2..dd389cf7f652e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22963,7 +22963,6 @@ "xpack.security.account.changePasswordTitle": "パスワード", "xpack.security.account.currentPasswordRequired": "現在のパスワードが必要です。", "xpack.security.account.noEmailMessage": "メールアドレスがありません", - "xpack.security.account.pageTitle": "{strongUsername}の設定", "xpack.security.account.passwordLengthDescription": "パスワードが短すぎます。", "xpack.security.account.passwordsDoNotMatch": "パスワードが一致していません。", "xpack.security.account.usernameGroupDescription": "この情報は変更できません。", @@ -23375,22 +23374,6 @@ "xpack.security.management.roles.statusColumnName": "ステータス", "xpack.security.management.roles.subtitle": "ユーザーのグループにロールを適用してスタック全体のパーミッションを管理します。", "xpack.security.management.rolesTitle": "ロール", - "xpack.security.management.users.changePasswordFlyout.confirmButton": "{isSubmitting, select, true{パスワードを変更しています…} other{パスワードの変更}}", - "xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError": "パスワードが一致していません。", - "xpack.security.management.users.changePasswordFlyout.confirmPasswordLabel": "パスワードの確認", - "xpack.security.management.users.changePasswordFlyout.confirmSystemPasswordButton": "{isSubmitting, select, true{パスワードを変更しています…} other{パスワードの変更}}", - "xpack.security.management.users.changePasswordFlyout.currentPasswordInvalidError": "無効なパスワードです。", - "xpack.security.management.users.changePasswordFlyout.currentPasswordLabel": "現在のパスワード", - "xpack.security.management.users.changePasswordFlyout.currentPasswordRequiredError": "現在のパスワードを入力してください。", - "xpack.security.management.users.changePasswordFlyout.errorMessage": "パスワードを変更できませんでした", - "xpack.security.management.users.changePasswordFlyout.passwordInvalidError": "パスワードは6文字以上でなければなりません。", - "xpack.security.management.users.changePasswordFlyout.passwordLabel": "新しいパスワード", - "xpack.security.management.users.changePasswordFlyout.passwordRequiredError": "新しいパスワードを入力してください。", - "xpack.security.management.users.changePasswordFlyout.successMessage": "'{username}'のパスワードが変更されました。", - "xpack.security.management.users.changePasswordFlyout.systemUserDescription": "変更後は、新しいパスワードを使用して手動で構成ファイルを更新し、Kibanaを再起動する必要があります。", - "xpack.security.management.users.changePasswordFlyout.systemUserTitle": "これは非常に重要です。", - "xpack.security.management.users.changePasswordFlyout.systemUserWarning": "このパスワードを変更すると、KibanaはElasticsearchと通信できません。", - "xpack.security.management.users.changePasswordFlyout.title": "パスワードを変更", "xpack.security.management.users.changePasswordFlyout.userLabel": "ユーザー", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "キャンセル", "xpack.security.management.users.confirmDelete.cannotUndoWarning": "削除したユーザーは復元できません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 796445b542ca1..f0d2ad90cd60b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22995,7 +22995,6 @@ "xpack.security.account.changePasswordTitle": "密码", "xpack.security.account.currentPasswordRequired": "当前密码必填。", "xpack.security.account.noEmailMessage": "没有电子邮件地址", - "xpack.security.account.pageTitle": "{strongUsername} 的设置", "xpack.security.account.passwordLengthDescription": "密码过短。", "xpack.security.account.passwordsDoNotMatch": "密码不匹配。", "xpack.security.account.usernameGroupDescription": "不能更改此信息。", @@ -23407,22 +23406,6 @@ "xpack.security.management.roles.statusColumnName": "状态", "xpack.security.management.roles.subtitle": "将角色应用到用户组并管理整个堆栈的权限。", "xpack.security.management.rolesTitle": "角色", - "xpack.security.management.users.changePasswordFlyout.confirmButton": "{isSubmitting, select, true{正在更改密码……} other{更改密码}}", - "xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError": "密码不匹配。", - "xpack.security.management.users.changePasswordFlyout.confirmPasswordLabel": "确认密码", - "xpack.security.management.users.changePasswordFlyout.confirmSystemPasswordButton": "{isSubmitting, select, true{正在更改密码……} other{更改密码}}", - "xpack.security.management.users.changePasswordFlyout.currentPasswordInvalidError": "密码无效。", - "xpack.security.management.users.changePasswordFlyout.currentPasswordLabel": "当前密码", - "xpack.security.management.users.changePasswordFlyout.currentPasswordRequiredError": "输入您的当前密码。", - "xpack.security.management.users.changePasswordFlyout.errorMessage": "无法更改密码", - "xpack.security.management.users.changePasswordFlyout.passwordInvalidError": "密码长度必须至少为 6 个字符。", - "xpack.security.management.users.changePasswordFlyout.passwordLabel": "新密码", - "xpack.security.management.users.changePasswordFlyout.passwordRequiredError": "输入新密码。", - "xpack.security.management.users.changePasswordFlyout.successMessage": "已为“{username}”更改密码。", - "xpack.security.management.users.changePasswordFlyout.systemUserDescription": "更改后,必须使用新密码手动更新您的配置文件,然后重新启动 Kibana。", - "xpack.security.management.users.changePasswordFlyout.systemUserTitle": "这极其重要!", - "xpack.security.management.users.changePasswordFlyout.systemUserWarning": "更改此密码将会阻止 Kibana 与 Elasticsearch 通信。", - "xpack.security.management.users.changePasswordFlyout.title": "更改密码", "xpack.security.management.users.changePasswordFlyout.userLabel": "用户", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "取消", "xpack.security.management.users.confirmDelete.cannotUndoWarning": "您无法恢复删除的用户。", diff --git a/x-pack/test/accessibility/apps/users.ts b/x-pack/test/accessibility/apps/users.ts index 5833a19580c24..71ed3a27c1073 100644 --- a/x-pack/test/accessibility/apps/users.ts +++ b/x-pack/test/accessibility/apps/users.ts @@ -98,7 +98,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.clickLinkText('deleteA11y'); await find.clickByButtonText('Change password'); await a11y.testAppSnapshot(); - await testSubjects.click('formFlyoutCancelButton'); + await testSubjects.click('changePasswordFormCancelButton'); }); it('a11y test for deactivate user screen', async () => { diff --git a/x-pack/test/api_integration/apis/security/basic_login.js b/x-pack/test/api_integration/apis/security/basic_login.js index ea8971d620231..c81034b6fb824 100644 --- a/x-pack/test/api_integration/apis/security/basic_login.js +++ b/x-pack/test/api_integration/apis/security/basic_login.js @@ -146,6 +146,7 @@ export default function ({ getService }) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be(validUsername); expect(apiResponse.body.authentication_provider).to.eql({ type: 'http', name: '__http__' }); @@ -192,6 +193,7 @@ export default function ({ getService }) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be(validUsername); expect(apiResponse.body.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); diff --git a/x-pack/test/functional/apps/security/user_email.ts b/x-pack/test/functional/apps/security/user_email.ts index f7878543bf3e7..44c375bdab10d 100644 --- a/x-pack/test/functional/apps/security/user_email.ts +++ b/x-pack/test/functional/apps/security/user_email.ts @@ -45,18 +45,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('login as new user and verify email', async function () { await PageObjects.security.login('newuser', 'changeme'); - await PageObjects.accountSetting.verifyAccountSettings('newuser@myEmail.com', 'newuser'); + await PageObjects.accountSetting.verifyAccountSettings('newuser'); }); it('click changepassword link, change the password and re-login', async function () { - await PageObjects.accountSetting.verifyAccountSettings('newuser@myEmail.com', 'newuser'); + await PageObjects.accountSetting.verifyAccountSettings('newuser'); await PageObjects.accountSetting.changePassword('changeme', 'mechange'); await PageObjects.security.forceLogout(); }); it('login as new user with changed password', async function () { await PageObjects.security.login('newuser', 'mechange'); - await PageObjects.accountSetting.verifyAccountSettings('newuser@myEmail.com', 'newuser'); + await PageObjects.accountSetting.verifyAccountSettings('newuser'); }); after(async function () { diff --git a/x-pack/test/functional/apps/security/users.ts b/x-pack/test/functional/apps/security/users.ts index 04c405b09407c..1bf0e2eea4d70 100644 --- a/x-pack/test/functional/apps/security/users.ts +++ b/x-pack/test/functional/apps/security/users.ts @@ -181,9 +181,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return toastCount >= 1; }); const successToast = await toasts.getToastElement(1); - expect(await successToast.getVisibleText()).to.be( - `Password changed for '${optionalUser.username}'.` - ); + expect(await successToast.getVisibleText()).to.be('Password successfully changed'); }); it('of current user when submitting form', async () => { @@ -202,9 +200,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return toastCount >= 1; }); const successToast = await toasts.getToastElement(1); - expect(await successToast.getVisibleText()).to.be( - `Password changed for '${optionalUser.username}'.` - ); + expect(await successToast.getVisibleText()).to.be('Password successfully changed'); }); }); diff --git a/x-pack/test/functional/page_objects/account_settings_page.ts b/x-pack/test/functional/page_objects/account_settings_page.ts index 5bf5be9030b75..85f644e8d42b9 100644 --- a/x-pack/test/functional/page_objects/account_settings_page.ts +++ b/x-pack/test/functional/page_objects/account_settings_page.ts @@ -9,27 +9,39 @@ import expect from '@kbn/expect'; import { FtrService } from '../ftr_provider_context'; export class AccountSettingsPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly userMenu = this.ctx.getService('userMenu'); - async verifyAccountSettings(expectedEmail: string, expectedUserName: string) { + async verifyAccountSettings(expectedUserName: string) { await this.userMenu.clickProvileLink(); const usernameField = await this.testSubjects.find('username'); const userName = await usernameField.getVisibleText(); expect(userName).to.be(expectedUserName); - const emailIdField = await this.testSubjects.find('email'); - const emailField = await emailIdField.getVisibleText(); - expect(emailField).to.be(expectedEmail); await this.userMenu.closeMenu(); } async changePassword(currentPassword: string, newPassword: string) { - await this.testSubjects.setValue('currentPassword', currentPassword); - await this.testSubjects.setValue('newPassword', newPassword); - await this.testSubjects.setValue('confirmNewPassword', newPassword); - await this.testSubjects.click('changePasswordButton'); - await this.testSubjects.existOrFail('passwordUpdateSuccess'); + await this.testSubjects.click('openChangePasswordForm'); + + const currentPasswordInput = await this.find.byName('current_password'); + await currentPasswordInput.clearValue(); + await currentPasswordInput.type(currentPassword); + + const passwordInput = await this.find.byName('password'); + await passwordInput.clearValue(); + await passwordInput.type(newPassword); + + const confirmPasswordInput = await this.find.byName('confirm_password'); + await confirmPasswordInput.clearValue(); + await confirmPasswordInput.type(newPassword); + + await this.testSubjects.click('changePasswordFormSubmitButton'); + + const toast = await this.testSubjects.find('euiToastHeader'); + const title = await toast.getVisibleText(); + expect(title).to.contain('Password successfully changed'); } } diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 508fb7106948a..3f4dc6056d8d2 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -497,7 +497,7 @@ export class SecurityPageObject extends FtrService { 'editUserChangePasswordConfirmPasswordInput', user.confirm_password ?? '' ); - await this.testSubjects.click('formFlyoutSubmitButton'); + await this.testSubjects.click('changePasswordFormSubmitButton'); } async updateUserProfile(user: UserFormValues) { diff --git a/x-pack/test/security_api_integration/tests/http_bearer/header.ts b/x-pack/test/security_api_integration/tests/http_bearer/header.ts index 7de7d83e154e2..1cc080c3b3e77 100644 --- a/x-pack/test/security_api_integration/tests/http_bearer/header.ts +++ b/x-pack/test/security_api_integration/tests/http_bearer/header.ts @@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) { ...authentication, authentication_provider: { name: '__http__', type: 'http' }, authentication_type: 'token', + elastic_cloud_user: false, }, }; } diff --git a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index 9eeb951c04e5c..091dfb1fb5ccc 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -157,6 +157,7 @@ export default function ({ getService }: FtrProviderContext) { lookup_realm: { name: 'kerb1', type: 'kerberos' }, authentication_provider: { type: 'kerberos', name: 'kerberos' }, authentication_type: 'token', + elastic_cloud_user: false, }); }); diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 2666277780be7..df7d309261a38 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -64,6 +64,7 @@ export default function ({ getService }: FtrProviderContext) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be(username); diff --git a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index 86d8fa2f77d2d..a5fad51792d30 100644 --- a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -225,6 +225,7 @@ export default function ({ getService }: FtrProviderContext) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be('user1'); @@ -278,6 +279,7 @@ export default function ({ getService }: FtrProviderContext) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be('user2'); diff --git a/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts index b71430193a14b..9a0483d5cebaa 100644 --- a/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts @@ -152,6 +152,7 @@ export default function ({ getService }: FtrProviderContext) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be('user1'); diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 1158a8a4d7fa7..ae9f3d893534b 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -155,6 +155,7 @@ export default function ({ getService }: FtrProviderContext) { lookup_realm: { name: 'pki1', type: 'pki' }, authentication_provider: { name: 'pki', type: 'pki' }, authentication_type: 'token', + elastic_cloud_user: false, }); }); @@ -192,6 +193,7 @@ export default function ({ getService }: FtrProviderContext) { lookup_realm: { name: 'pki1', type: 'pki' }, authentication_provider: { name: 'pki', type: 'pki' }, authentication_type: 'realm', + elastic_cloud_user: false, }); checkCookieIsSet(parseCookie(response.headers['set-cookie'][0])!); diff --git a/x-pack/test/security_api_integration/tests/saml/saml_login.ts b/x-pack/test/security_api_integration/tests/saml/saml_login.ts index f7b3c732e72ea..998e906a47415 100644 --- a/x-pack/test/security_api_integration/tests/saml/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml/saml_login.ts @@ -63,6 +63,7 @@ export default function ({ getService }: FtrProviderContext) { 'lookup_realm', 'authentication_provider', 'authentication_type', + 'elastic_cloud_user', ]); expect(apiResponse.body.username).to.be(username); diff --git a/yarn.lock b/yarn.lock index 369d99179cc15..4cb1bf8a2e7b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12524,7 +12524,7 @@ deep-object-diff@^1.1.0: resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== -deepmerge@3.2.0, deepmerge@^4.0.0, deepmerge@^4.2.2: +deepmerge@3.2.0, deepmerge@^2.1.1, deepmerge@^4.0.0, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -15104,6 +15104,19 @@ formidable@^1.2.0: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== +formik@^2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" + integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^1.10.0" + forwarded-parse@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/forwarded-parse/-/forwarded-parse-2.1.0.tgz#1ae9d7a4be3af884f74d936d856f7d8c6abd0439" @@ -19479,7 +19492,7 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash-es@^4.17.11: +lodash-es@^4.17.11, lodash-es@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== @@ -23964,7 +23977,7 @@ react-error-boundary@^3.1.0: dependencies: "@babel/runtime" "^7.12.5" -react-fast-compare@^2.0.4: +react-fast-compare@^2.0.1, react-fast-compare@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== @@ -27937,7 +27950,7 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== -tiny-warning@^1.0.0, tiny-warning@^1.0.3: +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== From f5faacd51164b74c2dcec3f6f71fce0f8280bb29 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 9 Jun 2022 09:06:15 +0200 Subject: [PATCH 17/44] Clean up vega event handler code (#133557) * better fallback * fix * Fixes normalizeString failure when index is undefined * Fix typo causing normalizeDate to not work properly * Adds recursive function to check for the toJSON property on a nested object * Apply PR comments * Check if object exists * Move check on the top * Some refactoring * Check object for function property * Address PR comment * Address Pr comments * Improve the NaN check * Address more PR comments * Add some unit tests * Update src/plugins/vis_types/vega/public/vega_view/utils.ts Co-authored-by: Aleh Zasypkin Co-authored-by: Stratoula Kalafateli Co-authored-by: Stratoula Kalafateli Co-authored-by: Aleh Zasypkin --- .../vega/public/vega_view/utils.test.ts | 70 +++++++++++++++++++ .../vis_types/vega/public/vega_view/utils.ts | 58 +++++++++++++++ .../vega/public/vega_view/vega_base_view.js | 18 +++-- 3 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 src/plugins/vis_types/vega/public/vega_view/utils.test.ts create mode 100644 src/plugins/vis_types/vega/public/vega_view/utils.ts diff --git a/src/plugins/vis_types/vega/public/vega_view/utils.test.ts b/src/plugins/vis_types/vega/public/vega_view/utils.test.ts new file mode 100644 index 0000000000000..020bdd4d117dd --- /dev/null +++ b/src/plugins/vis_types/vega/public/vega_view/utils.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { normalizeString, normalizeObject, normalizeDate } from './utils'; + +describe('normalizeString', () => { + test('should return undefined for non string input', async () => { + expect(normalizeString({})).toBe(undefined); + expect(normalizeString(12344)).toBe(undefined); + expect(normalizeString(null)).toBe(undefined); + }); + + test('should return the string for string input', async () => { + expect(normalizeString('logstash')).toBe('logstash'); + }); +}); + +describe('normalizeDate', () => { + test('should return timestamp if timestamp is given', async () => { + expect(normalizeDate(1654702414)).toBe(1654702414); + }); + + test('should return null if NaN is given', async () => { + expect(normalizeDate(NaN)).toBe(null); + }); + + test('should return date if a date object is given', async () => { + const date = Date.now(); + expect(normalizeDate(date)).toBe(date); + }); + + test('should return undefined for a string', async () => { + expect(normalizeDate('test')).toBe('test'); + }); + + test('should return the object if object is given', async () => { + expect(normalizeDate({ test: 'test' })).toStrictEqual({ test: 'test' }); + }); +}); + +describe('normalizeObject', () => { + test('should throw if a function is given as the object property', async () => { + expect(() => { + normalizeObject({ toJSON: () => alert('gotcha') }); + }).toThrow('a function cannot be used as a property name'); + }); + + test('should throw if a function is given on a nested object', async () => { + expect(() => { + normalizeObject({ test: { toJSON: () => alert('gotcha') } }); + }).toThrow('a function cannot be used as a property name'); + }); + + test('should return null for null', async () => { + expect(normalizeObject(null)).toBe(null); + }); + + test('should return null for undefined', async () => { + expect(normalizeObject(undefined)).toBe(null); + }); + + test('should return the object', async () => { + expect(normalizeObject({ test: 'test' })).toStrictEqual({ test: 'test' }); + }); +}); diff --git a/src/plugins/vis_types/vega/public/vega_view/utils.ts b/src/plugins/vis_types/vega/public/vega_view/utils.ts new file mode 100644 index 0000000000000..7e2833a869b6c --- /dev/null +++ b/src/plugins/vis_types/vega/public/vega_view/utils.ts @@ -0,0 +1,58 @@ +/* + * 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 { ensureNoUnsafeProperties } from '@kbn/std'; + +export function normalizeDate(date: unknown) { + if (typeof date === 'number') { + return !isNaN(date) ? date : null; + } else if (date instanceof Date) { + return date; + } else { + return normalizeObject(date); + } +} + +/* +Recursive function to check a nested object for a function property +This function should run before JSON.stringify to ensure that functions such as toJSON +are not invoked. We dont use the replacer function as it doesnt catch the toJSON function +*/ +export function checkObjectForFunctionProperty(object: unknown): boolean { + if (object === null || object === undefined) { + return false; + } + if (typeof object === 'function') { + return true; + } + if (object && typeof object === 'object') { + return Object.values(object).some( + (value) => typeof value === 'function' || checkObjectForFunctionProperty(value) + ); + } + + return false; +} +/* + We want to be strict here when an object is passed to a Vega function + - NaN (will be converted to null) + - undefined (key will be removed) + - Date (will be replaced by its toString value) + - will throw an error when a function is found + */ +export function normalizeObject(object: unknown) { + if (checkObjectForFunctionProperty(object)) { + throw new Error('a function cannot be used as a property name'); + } + const normalizedObject = object ? JSON.parse(JSON.stringify(object)) : null; + ensureNoUnsafeProperties(normalizedObject); + return normalizedObject; +} + +export function normalizeString(string: unknown) { + return typeof string === 'string' ? string : undefined; +} diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js index 90112e294d56b..5ce69e0f13863 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js @@ -20,6 +20,7 @@ import { TooltipHandler } from './vega_tooltip'; import { getEnableExternalUrls, getDataViews } from '../services'; import { extractIndexPatternsFromSpec } from '../lib/extract_index_pattern'; +import { normalizeDate, normalizeString, normalizeObject } from './utils'; scheme('elastic', euiPaletteColorBlind()); @@ -350,8 +351,11 @@ export class VegaBaseView { * @param {string} Elastic Query DSL's Custom label for kibanaAddFilter, as used in '+ Add Filter' */ async addFilterHandler(query, index, alias) { - const indexId = await this.findIndex(index); - const filter = buildQueryFilter(query, indexId, alias); + const normalizedQuery = normalizeObject(query); + const normalizedIndex = normalizeString(index); + const normalizedAlias = normalizeString(alias); + const indexId = await this.findIndex(normalizedIndex); + const filter = buildQueryFilter(normalizedQuery, indexId, normalizedAlias); this._fireEvent({ name: 'applyFilter', data: { filters: [filter] } }); } @@ -361,8 +365,10 @@ export class VegaBaseView { * @param {string} [index] as defined in Kibana, or default if missing */ async removeFilterHandler(query, index) { - const indexId = await this.findIndex(index); - const filterToRemove = buildQueryFilter(query, indexId); + const normalizedQuery = normalizeObject(query); + const normalizedIndex = normalizeString(index); + const indexId = await this.findIndex(normalizedIndex); + const filterToRemove = buildQueryFilter(normalizedQuery, indexId); const currentFilters = this._filterManager.getFilters(); const existingFilter = currentFilters.find((filter) => compareFilters(filter, filterToRemove)); @@ -386,7 +392,9 @@ export class VegaBaseView { * @param {number|string|Date} end */ setTimeFilterHandler(start, end) { - const { from, to, mode } = VegaBaseView._parseTimeRange(start, end); + const normalizedStart = normalizeDate(start); + const normalizedEnd = normalizeDate(end); + const { from, to, mode } = VegaBaseView._parseTimeRange(normalizedStart, normalizedEnd); this._fireEvent({ name: 'applyFilter', From 47ffdd83d45f3f177c30ebb28da60b6f51d86767 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 9 Jun 2022 09:32:26 +0200 Subject: [PATCH 18/44] [Cases][SecOps] Fix bug where observables are not created (#133752) * Push observables: * Filter docs without _source --- .../cases/server/client/alerts/get.test.ts | 138 ++++++++++++++++++ .../plugins/cases/server/client/alerts/get.ts | 10 +- .../server/services/alerts/index.test.ts | 77 +++++++++- .../cases/server/services/alerts/index.ts | 12 +- 4 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/cases/server/client/alerts/get.test.ts diff --git a/x-pack/plugins/cases/server/client/alerts/get.test.ts b/x-pack/plugins/cases/server/client/alerts/get.test.ts new file mode 100644 index 0000000000000..be675ed20a5cb --- /dev/null +++ b/x-pack/plugins/cases/server/client/alerts/get.test.ts @@ -0,0 +1,138 @@ +/* + * 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 { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { AlertService } from '../../services'; +import { CasesClientArgs } from '../types'; +import { getAlerts } from './get'; + +describe('getAlerts', () => { + const esClient = elasticsearchServiceMock.createElasticsearchClient(); + const logger = loggingSystemMock.create().get('case'); + let alertsService: AlertService; + + beforeEach(async () => { + alertsService = new AlertService(esClient, logger); + jest.clearAllMocks(); + }); + + const docs = [ + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + _version: 2, + _seq_no: 255, + _primary_term: 1, + found: true, + _source: { + destination: { mac: 'ff:ff:ff:ff:ff:ff' }, + source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 }, + ecs: { version: '8.0.0' }, + }, + }, + ]; + + esClient.mget.mockResolvedValue({ docs }); + + it('returns an empty array if the alert info are empty', async () => { + const clientArgs = { alertsService } as unknown as CasesClientArgs; + const res = await getAlerts([], clientArgs); + + expect(res).toEqual([]); + }); + + it('returns the alerts correctly', async () => { + const clientArgs = { alertsService } as unknown as CasesClientArgs; + const res = await getAlerts( + [ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + }, + ], + clientArgs + ); + + expect(res).toEqual([ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + destination: { mac: 'ff:ff:ff:ff:ff:ff' }, + source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 }, + ecs: { version: '8.0.0' }, + }, + ]); + }); + + it('filters mget errors correctly', async () => { + esClient.mget.mockResolvedValue({ + docs: [ + ...docs, + { + error: { type: 'not-found', reason: 'an error' }, + _index: '.internal.alerts-security.alerts-default-000002', + _id: 'd3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + }, + ], + }); + const clientArgs = { alertsService } as unknown as CasesClientArgs; + + const res = await getAlerts( + [ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + }, + ], + clientArgs + ); + + expect(res).toEqual([ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + destination: { mac: 'ff:ff:ff:ff:ff:ff' }, + source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 }, + ecs: { version: '8.0.0' }, + }, + ]); + }); + + it('filters docs without _source correctly', async () => { + esClient.mget.mockResolvedValue({ + docs: [ + ...docs, + { + _index: '.internal.alerts-security.alerts-default-000002', + _id: 'd3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + found: true, + }, + ], + }); + const clientArgs = { alertsService } as unknown as CasesClientArgs; + + const res = await getAlerts( + [ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + }, + ], + clientArgs + ); + + expect(res).toEqual([ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + destination: { mac: 'ff:ff:ff:ff:ff:ff' }, + source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 }, + ecs: { version: '8.0.0' }, + }, + ]); + }); +}); diff --git a/x-pack/plugins/cases/server/client/alerts/get.ts b/x-pack/plugins/cases/server/client/alerts/get.ts index 831ab0b05ed17..6a3f09cf30a89 100644 --- a/x-pack/plugins/cases/server/client/alerts/get.ts +++ b/x-pack/plugins/cases/server/client/alerts/get.ts @@ -5,9 +5,17 @@ * 2.0. */ +import { MgetResponseItem, GetGetResult } from '@elastic/elasticsearch/lib/api/types'; import { CasesClientGetAlertsResponse } from './types'; import { CasesClientArgs } from '..'; import { AlertInfo } from '../../common/types'; +import { Alert } from '../../services/alerts'; + +function isAlert( + doc?: MgetResponseItem +): doc is Omit, '_source'> & { _source: Alert } { + return Boolean(doc && !('error' in doc) && '_source' in doc); +} export const getAlerts = async ( alertsInfo: AlertInfo[], @@ -23,7 +31,7 @@ export const getAlerts = async ( return []; } - return alerts.docs.map((alert) => ({ + return alerts.docs.filter(isAlert).map((alert) => ({ id: alert._id, index: alert._index, ...alert._source, diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts index 02e58b3a4e5ac..24eef0a88fcad 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.test.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts @@ -12,15 +12,14 @@ import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mo describe('updateAlertsStatus', () => { const esClient = elasticsearchServiceMock.createElasticsearchClient(); const logger = loggingSystemMock.create().get('case'); + let alertService: AlertService; - describe('happy path', () => { - let alertService: AlertService; - - beforeEach(async () => { - alertService = new AlertService(esClient, logger); - jest.resetAllMocks(); - }); + beforeEach(async () => { + alertService = new AlertService(esClient, logger); + jest.clearAllMocks(); + }); + describe('happy path', () => { it('updates the status of the alert correctly', async () => { const args = [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }]; @@ -273,4 +272,68 @@ describe('updateAlertsStatus', () => { expect(esClient.updateByQuery).not.toHaveBeenCalled(); }); }); + + describe('getAlerts', () => { + const docs = [ + { + _index: '.internal.alerts-security.alerts-default-000001', + _id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + _version: 2, + _seq_no: 255, + _primary_term: 1, + found: true, + _source: { + destination: { mac: 'ff:ff:ff:ff:ff:ff' }, + source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 }, + ecs: { version: '8.0.0' }, + }, + }, + ]; + + esClient.mget.mockResolvedValue({ docs }); + + it('returns the alerts correctly', async () => { + const res = await alertService.getAlerts([ + { + index: '.internal.alerts-security.alerts-default-000001', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + }, + ]); + + expect(esClient.mget).toHaveBeenCalledWith({ + body: { + docs: [ + { + _id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + _index: '.internal.alerts-security.alerts-default-000001', + }, + ], + }, + }); + + expect(res).toEqual({ docs }); + }); + + it('returns undefined if the id is empty', async () => { + const res = await alertService.getAlerts([ + { + index: '.internal.alerts-security.alerts-default-000001', + id: '', + }, + ]); + + expect(res).toBe(undefined); + }); + + it('returns undefined if the index is empty', async () => { + const res = await alertService.getAlerts([ + { + index: '', + id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f', + }, + ]); + + expect(res).toBe(undefined); + }); + }); }); diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index b219c50964d39..a83528d026fd0 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -13,6 +13,7 @@ import { ALERT_WORKFLOW_STATUS, STATUS_VALUES, } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; +import { MgetResponse } from '@elastic/elasticsearch/lib/api/types'; import { CaseStatuses } from '../../../common/api'; import { MAX_ALERTS_PER_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { createCaseError } from '../../common/error'; @@ -180,7 +181,7 @@ export class AlertService { ); } - public async getAlerts(alertsInfo: AlertInfo[]): Promise { + public async getAlerts(alertsInfo: AlertInfo[]): Promise | undefined> { try { const docs = alertsInfo .filter((alert) => !AlertService.isEmptyAlert(alert)) @@ -193,8 +194,7 @@ export class AlertService { const results = await this.scopedClusterClient.mget({ body: { docs } }); - // @ts-expect-error @elastic/elasticsearch _source is optional - return results.body; + return results; } catch (error) { throw createCaseError({ message: `Failed to retrieve alerts ids: ${JSON.stringify(alertsInfo)}: ${error}`, @@ -230,16 +230,12 @@ function updateIndexEntryWithStatus( } } -interface Alert { +export interface Alert { _id: string; _index: string; _source: Record; } -interface AlertsResponse { - docs: Alert[]; -} - interface AlertIdIndex { id: string; index: string; From 09a29d207afdd7871f1a7b7ad8fe141973fc36b9 Mon Sep 17 00:00:00 2001 From: Or Ouziel Date: Thu, 9 Jun 2022 10:34:18 +0300 Subject: [PATCH 19/44] [Cloud Posture] fix tab scroll position (#133865) --- .../public/pages/findings/findings_flyout/findings_flyout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx index 886683ed098ca..c5d43df875a47 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx @@ -107,7 +107,7 @@ export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) => ))} - + From 268898a654423b7308ccad2ba9e712a980a83359 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 9 Jun 2022 09:47:59 +0200 Subject: [PATCH 20/44] [Uptime] Fix lookback window for anomalies for anomaly alert (#93389) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/legacy_uptime/lib/alerts/duration_anomaly.test.ts | 2 +- .../server/legacy_uptime/lib/alerts/duration_anomaly.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.test.ts index 7f67d9f0ae181..2a1f4bb59fd0d 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.test.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.test.ts @@ -164,7 +164,7 @@ describe('duration anomaly alert', () => { [], 'auto', options.params.severity, - 1620909217000, + 1620907417000, 1620909217000, 'UTC', 500, diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts index 3c3d17775a510..05ffd84726113 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts @@ -68,7 +68,9 @@ const getAnomalies = async ( [], 'auto', params.severity, - moment(lastCheckedAt).valueOf(), + // Lookback window will be 2x Bucket time span, for uptime job, for now bucket + // timespan will always be 15minute + moment(lastCheckedAt).subtract(30, 'minute').valueOf(), moment().valueOf(), Intl.DateTimeFormat().resolvedOptions().timeZone, 500, From 3c10fe1c0548ffe1339e5e2779d9a6649d944016 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 9 Jun 2022 10:59:49 +0200 Subject: [PATCH 21/44] [Case] Migrate severity to the create case user action (#133607) * Migrate severity * Do not migrate other user actions * More tests * Fix tests * PR feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../migrations/user_actions/index.ts | 2 + .../migrations/user_actions/severity.test.ts | 76 ++++++++++++++++ .../migrations/user_actions/severity.ts | 23 +++++ .../tests/common/user_actions/migrations.ts | 86 ++++++++++++++++++- 4 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.test.ts create mode 100644 x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.ts diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts index eaabacf6ed93e..fb143a0ccec49 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts @@ -14,6 +14,7 @@ import { ConnectorTypes } from '../../../../common/api'; import { removeRuleInformation } from './alerts'; import { userActionsConnectorIdMigration } from './connector_id'; import { payloadMigration } from './payload'; +import { addSeverityToCreateUserAction } from './severity'; import { UserActions } from './types'; export const userActionsMigrations = { @@ -63,4 +64,5 @@ export const userActionsMigrations = { '7.16.0': userActionsConnectorIdMigration, '8.0.0': removeRuleInformation, '8.1.0': payloadMigration, + '8.3.0': addSeverityToCreateUserAction, }; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.test.ts new file mode 100644 index 0000000000000..4e46a913682bb --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { ActionTypes } from '../../../../common/api'; +import { CASE_USER_ACTION_SAVED_OBJECT } from '../../../../common/constants'; +import { addSeverityToCreateUserAction } from './severity'; + +describe('severity migration', () => { + const userAction = { + type: CASE_USER_ACTION_SAVED_OBJECT, + id: '1', + attributes: { + action_at: '2022-01-09T22:00:00.000Z', + action_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic User', + username: 'elastic', + }, + payload: {}, + type: ActionTypes.create_case, + }, + }; + + it('adds the severity field to the create_case user action', () => { + // @ts-expect-error + const migratedUserAction = addSeverityToCreateUserAction(userAction); + expect(migratedUserAction).toEqual({ + attributes: { + action_at: '2022-01-09T22:00:00.000Z', + action_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic User', + username: 'elastic', + }, + payload: { + severity: 'low', + }, + type: 'create_case', + }, + id: '1', + references: [], + type: 'cases-user-actions', + }); + }); + + it('does NOT add the severity field to the create_case user action', () => { + Object.keys(ActionTypes) + .filter((type) => type !== ActionTypes.create_case) + .forEach((type) => { + const migratedUserAction = addSeverityToCreateUserAction({ + ...userAction, + // @ts-expect-error + attributes: { ...userAction.attributes, type }, + }); + expect(migratedUserAction).toEqual({ + attributes: { + action_at: '2022-01-09T22:00:00.000Z', + action_by: { + email: 'elastic@elastic.co', + full_name: 'Elastic User', + username: 'elastic', + }, + payload: {}, + type, + }, + id: '1', + references: [], + type: 'cases-user-actions', + }); + }); + }); +}); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.ts new file mode 100644 index 0000000000000..6e838e7bcbc1d --- /dev/null +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/severity.ts @@ -0,0 +1,23 @@ +/* + * 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 { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '@kbn/core/server'; +import { ActionTypes, CaseSeverity, CreateCaseUserAction } from '../../../../common/api'; + +export const addSeverityToCreateUserAction = ( + doc: SavedObjectUnsanitizedDoc +): SavedObjectSanitizedDoc => { + if (doc.attributes.type !== ActionTypes.create_case) { + return { ...doc, references: doc.references ?? [] }; + } + + const payload = { + ...doc.attributes.payload, + severity: doc?.attributes?.payload?.severity ?? CaseSeverity.LOW, + }; + return { ...doc, attributes: { ...doc.attributes, payload }, references: doc.references ?? [] }; +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts index 9bf55018bbbf4..ebcd1fd389b36 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { CASES_URL, SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common/constants'; -import { CaseUserActionsResponse, CommentType } from '@kbn/cases-plugin/common/api'; +import { ActionTypes, CaseUserActionsResponse, CommentType } from '@kbn/cases-plugin/common/api'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { deleteAllCaseItems, getCaseUserActions } from '../../../../common/lib/utils'; @@ -77,6 +77,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { fields: null, id: 'none', }, + severity: 'low', owner: 'securitySolution', settings: { syncAlerts: true }, }, @@ -189,6 +190,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { settings: { syncAlerts: true, }, + severity: 'low', owner: 'securitySolution', }, type: 'create_case', @@ -295,6 +297,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { settings: { syncAlerts: true, }, + severity: 'low', owner: 'securitySolution', }, type: 'create_case', @@ -323,6 +326,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { settings: { syncAlerts: true, }, + severity: 'low', }, type: 'create_case', action_id: 'b3094de0-005e-11ec-91f1-6daf2ab59fb5', @@ -367,6 +371,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { syncAlerts: true, }, owner: 'securitySolution', + severity: 'low', }, type: 'create_case', action_id: 'e7882d70-005e-11ec-91f1-6daf2ab59fb5', @@ -738,6 +743,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { tags: ['user', 'actions'], title: 'User actions', owner: 'securitySolution', + severity: 'low', }, type: 'create_case', }, @@ -1046,6 +1052,84 @@ export default function createGetTests({ getService }: FtrProviderContext) { ]); }); }); + + describe('8.3.0', () => { + const CASE_ID = '5257a000-5e7d-11ec-9ee9-cd64f0b77b3c'; + const CREATE_UA_ID = '5275af50-5e7d-11ec-9ee9-cd64f0b77b3c'; + + before(async () => { + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.0.0/cases.json' + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.0.0/cases.json' + ); + await deleteAllCaseItems(es); + }); + + describe('add severity', () => { + it('adds the severity field to the create case user action', async () => { + const userActions = await getCaseUserActions({ + supertest, + caseID: CASE_ID, + }); + + const createUserAction = userActions.find( + (userAction) => userAction.action_id === CREATE_UA_ID + ); + + expect(createUserAction).to.eql({ + action: 'create', + action_id: '5275af50-5e7d-11ec-9ee9-cd64f0b77b3c', + case_id: '5257a000-5e7d-11ec-9ee9-cd64f0b77b3c', + comment_id: null, + created_at: '2021-12-16T14:34:48.709Z', + created_by: { + email: '', + full_name: '', + username: 'elastic', + }, + owner: 'securitySolution', + payload: { + connector: { + fields: null, + id: 'none', + name: 'none', + type: '.none', + }, + description: 'migrating user actions', + settings: { + syncAlerts: true, + }, + status: 'open', + tags: ['user', 'actions'], + title: 'User actions', + owner: 'securitySolution', + severity: 'low', + }, + type: 'create_case', + }); + }); + + it('does NOT add the severity field to the other user actions', async () => { + const userActions = await getCaseUserActions({ + supertest, + caseID: CASE_ID, + }); + + const userActionsWithoutCreateAction = userActions.filter( + (userAction) => userAction.type !== ActionTypes.create_case + ); + + for (const userAction of userActionsWithoutCreateAction) { + expect(userAction.payload).not.to.have.property('severity'); + } + }); + }); + }); }); } From 3e38c9814b2b72723e6cddd7015cd58d2732ea4f Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 9 Jun 2022 11:05:28 +0200 Subject: [PATCH 22/44] [APM] Backend operations list view (#133653) --- .../backend_detail_operations_list/index.tsx | 180 ++++++++++++ .../app/backend_detail_operations/index.tsx | 52 ++++ .../app/backend_detail_overview/index.tsx | 38 +-- .../app/backend_detail_view/index.tsx | 67 +++++ .../index.tsx | 2 +- .../app/backend_inventory/index.tsx | 2 +- .../backend_operation_detail_link.tsx | 25 ++ .../components/app/service_overview/index.tsx | 1 - .../index.tsx | 3 - .../public/components/routing/app_root.tsx | 53 ++-- .../public/components/routing/home/index.tsx | 89 ++++-- ...direct_backends_to_backend_detail_view.tsx | 19 ++ ...redirect_backends_to_backend_inventory.tsx | 34 +++ ... => redirect_path_backend_detail_view.tsx} | 2 +- .../templates/backend_detail_template.tsx | 60 +++- .../get_span_metric_columns.tsx | 215 ++++++++++++++ .../shared/dependencies_table/index.tsx | 227 +++------------ .../shared/is_route_with_time_range.ts | 1 + ...se_operations_breakdown_enabled_setting.ts | 15 + x-pack/plugins/apm/public/plugin.ts | 8 +- .../backends/get_top_backend_operations.ts | 192 +++++++++++++ .../apm/server/routes/backends/route.ts | 40 +++ x-pack/plugins/observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 1 + .../observability/server/ui_settings.ts | 16 ++ .../dependencies/top_dependencies.spec.ts | 2 +- .../tests/dependencies/top_operations.spec.ts | 271 ++++++++++++++++++ 27 files changed, 1331 insertions(+), 285 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/backend_detail_operations/backend_detail_operations_list/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/backend_detail_operations/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/backend_detail_view/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/backend_operation_detail_view/backend_operation_detail_link.tsx create mode 100644 x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_detail_view.tsx create mode 100644 x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_inventory.tsx rename x-pack/plugins/apm/public/components/routing/home/{redirect_to_backend_overview_route_view.tsx => redirect_path_backend_detail_view.tsx} (92%) create mode 100644 x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx create mode 100644 x-pack/plugins/apm/public/hooks/use_operations_breakdown_enabled_setting.ts create mode 100644 x-pack/plugins/apm/server/routes/backends/get_top_backend_operations.ts create mode 100644 x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_operations/backend_detail_operations_list/index.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_operations/backend_detail_operations_list/index.tsx new file mode 100644 index 0000000000000..080852f017cd8 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/backend_detail_operations/backend_detail_operations_list/index.tsx @@ -0,0 +1,180 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { keyBy } from 'lodash'; +import React from 'react'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useBreakpoints } from '../../../../hooks/use_breakpoints'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { + getSpanMetricColumns, + SpanMetricGroup, +} from '../../../shared/dependencies_table/get_span_metric_columns'; +import { EmptyMessage } from '../../../shared/empty_message'; +import { ITableColumn, ManagedTable } from '../../../shared/managed_table'; +import { getComparisonEnabled } from '../../../shared/time_comparison/get_comparison_enabled'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; +import { BackendOperationDetailLink } from '../../backend_operation_detail_view/backend_operation_detail_link'; + +interface OperationStatisticsItem extends SpanMetricGroup { + spanName: string; +} + +function OperationLink({ spanName }: { spanName: string }) { + const { query } = useApmParams('/backends/operations'); + + return ( + } + /> + ); +} + +export function BackendDetailOperationsList() { + const { + query: { + rangeFrom, + rangeTo, + backendName, + environment, + kuery, + comparisonEnabled: urlComparisonEnabled, + offset, + }, + } = useApmParams('/backends/operations'); + + const { core } = useApmPluginContext(); + + const breakpoints = useBreakpoints(); + + const { start, end } = useTimeRange({ + rangeFrom, + rangeTo, + }); + + const comparisonEnabled = getComparisonEnabled({ + core, + urlComparisonEnabled, + }); + + const primaryStatsFetch = useFetcher( + (callApmApi) => { + return callApmApi('GET /internal/apm/backends/operations', { + params: { + query: { + backendName, + start, + end, + environment, + kuery, + }, + }, + }); + }, + [backendName, start, end, environment, kuery] + ); + + const comparisonStatsFetch = useFetcher( + (callApmApi) => { + if (!comparisonEnabled) { + return; + } + return callApmApi('GET /internal/apm/backends/operations', { + params: { + query: { + backendName, + start, + end, + offset, + environment, + kuery, + }, + }, + }); + }, + [backendName, start, end, offset, environment, kuery, comparisonEnabled] + ); + + const columns: Array> = [ + { + name: i18n.translate( + 'xpack.apm.backendDetailOperationsList.spanNameColumnLabel', + { + defaultMessage: 'Span name', + } + ), + field: 'spanName', + sortable: true, + render: (_, { spanName }) => , + }, + ...getSpanMetricColumns({ + breakpoints, + comparisonFetchStatus: comparisonStatsFetch.status, + }), + ]; + + const comparisonOperationsBySpanName = keyBy( + comparisonStatsFetch.data?.operations, + 'spanName' + ); + + const noItemsMessage = ( + + ); + + const items = + primaryStatsFetch.data?.operations.map( + (operation): OperationStatisticsItem => { + const comparisonOperation = + comparisonOperationsBySpanName[operation.spanName]; + + return { + spanName: operation.spanName, + latency: operation.latency, + throughput: operation.throughput, + failureRate: operation.failureRate, + impact: operation.impact, + currentStats: { + latency: operation.timeseries.latency, + throughput: operation.timeseries.throughput, + failureRate: operation.timeseries.failureRate, + }, + previousStats: comparisonOperation + ? { + latency: comparisonOperation.timeseries.latency, + throughput: comparisonOperation.timeseries.throughput, + failureRate: comparisonOperation.timeseries.failureRate, + impact: comparisonOperation.impact, + } + : undefined, + }; + } + ) ?? []; + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_operations/index.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_operations/index.tsx new file mode 100644 index 0000000000000..f26f9d66833ee --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/backend_detail_operations/index.tsx @@ -0,0 +1,52 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { BackendDetailOperationsList } from './backend_detail_operations_list'; + +export function BackendDetailOperations() { + const { + query: { + backendName, + rangeFrom, + rangeTo, + refreshInterval, + refreshPaused, + environment, + kuery, + comparisonEnabled, + }, + } = useApmParams('/backends/operations'); + + const apmRouter = useApmRouter(); + + useBreadcrumb([ + { + title: i18n.translate( + 'xpack.apm.backendDetailOperations.breadcrumbTitle', + { defaultMessage: 'Operations' } + ), + href: apmRouter.link('/backends/operations', { + query: { + backendName, + rangeFrom, + rangeTo, + refreshInterval, + refreshPaused, + environment, + kuery, + comparisonEnabled, + }, + }), + }, + ]); + + return ; +} diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx index 811f8979c8c96..76089c4c756ac 100644 --- a/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_detail_overview/index.tsx @@ -15,17 +15,10 @@ import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; -import { SearchBar } from '../../shared/search_bar'; import { BackendLatencyChart } from './backend_latency_chart'; -import { DependenciesInventoryTitle } from '../../routing/home'; import { BackendDetailDependenciesTable } from './backend_detail_dependencies_table'; import { BackendThroughputChart } from './backend_throughput_chart'; import { BackendFailedTransactionRateChart } from './backend_error_rate_chart'; -import { BackendDetailTemplate } from '../../routing/templates/backend_detail_template'; -import { - getKueryBarBoolFilter, - kueryBarPlaceholder, -} from '../../../../common/backends'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; export function BackendDetailOverview() { @@ -46,21 +39,9 @@ export function BackendDetailOverview() { useBreadcrumb([ { - title: DependenciesInventoryTitle, - href: apmRouter.link('/backends', { - query: { - rangeFrom, - rangeTo, - refreshInterval, - refreshPaused, - environment, - kuery, - comparisonEnabled, - }, + title: i18n.translate('xpack.apm.backendDetailOverview.breadcrumbTitle', { + defaultMessage: 'Overview', }), - }, - { - title: backendName, href: apmRouter.link('/backends/overview', { query: { backendName, @@ -75,21 +56,10 @@ export function BackendDetailOverview() { }), }, ]); - - const kueryBarBoolFilter = getKueryBarBoolFilter({ - environment, - backendName, - }); - const largeScreenOrSmaller = useBreakpoints().isLarge; return ( - - + <> - + ); } diff --git a/x-pack/plugins/apm/public/components/app/backend_detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/backend_detail_view/index.tsx new file mode 100644 index 0000000000000..086e5dbea36a2 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/backend_detail_view/index.tsx @@ -0,0 +1,67 @@ +/* + * 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 React from 'react'; +import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { DependenciesInventoryTitle } from '../../routing/home'; +import { BackendDetailTemplate } from '../../routing/templates/backend_detail_template'; + +export function BackendDetailView({ + children, +}: { + children: React.ReactChild; +}) { + const { + query: { + backendName, + rangeFrom, + rangeTo, + refreshInterval, + refreshPaused, + environment, + kuery, + comparisonEnabled, + }, + } = useApmParams('/backends'); + + const apmRouter = useApmRouter(); + + useBreadcrumb([ + { + title: DependenciesInventoryTitle, + href: apmRouter.link('/backends/inventory', { + query: { + rangeFrom, + rangeTo, + refreshInterval, + refreshPaused, + environment, + kuery, + comparisonEnabled, + }, + }), + }, + { + title: backendName, + href: apmRouter.link('/backends', { + query: { + backendName, + rangeFrom, + rangeTo, + refreshInterval, + refreshPaused, + environment, + kuery, + comparisonEnabled, + }, + }), + }, + ]); + + return {children}; +} diff --git a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx index ace57ae0769b9..eff0f1c4474cc 100644 --- a/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_inventory/backend_inventory_dependencies_table/index.tsx @@ -27,7 +27,7 @@ export function BackendInventoryDependenciesTable() { comparisonEnabled, offset, }, - } = useApmParams('/backends'); + } = useApmParams('/backends/inventory'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); diff --git a/x-pack/plugins/apm/public/components/app/backend_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/backend_inventory/index.tsx index f749927826231..f29724fa7cdcb 100644 --- a/x-pack/plugins/apm/public/components/app/backend_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/backend_inventory/index.tsx @@ -18,7 +18,7 @@ import { BackendInventoryDependenciesTable } from './backend_inventory_dependenc export function BackendInventory() { const { query: { environment }, - } = useApmParams('/backends'); + } = useApmParams('/backends/inventory'); const kueryBarBoolFilter = getKueryBarBoolFilter({ environment, }); diff --git a/x-pack/plugins/apm/public/components/app/backend_operation_detail_view/backend_operation_detail_link.tsx b/x-pack/plugins/apm/public/components/app/backend_operation_detail_view/backend_operation_detail_link.tsx new file mode 100644 index 0000000000000..da3c4e0645f0c --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/backend_operation_detail_view/backend_operation_detail_link.tsx @@ -0,0 +1,25 @@ +/* + * 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 React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { TypeOf } from '@kbn/typed-react-router-config'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { ApmRoutes } from '../../routing/apm_route_config'; + +type Query = TypeOf['query']; + +export function BackendOperationDetailLink(query: Query) { + const router = useApmRouter(); + + const { spanName } = query; + + const link = router.link('/backends/operation', { + query, + }); + + return {spanName}; +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 078b797b84ae8..708e04f25afa5 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -184,7 +184,6 @@ export function ServiceOverview() { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 7f5cd994ff8c9..7fb66b60a1788 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -22,14 +22,12 @@ import { ServiceLink } from '../../../shared/service_link'; interface ServiceOverviewDependenciesTableProps { fixedHeight?: boolean; - isSingleColumn?: boolean; link?: ReactNode; showPerPageOptions?: boolean; } export function ServiceOverviewDependenciesTable({ fixedHeight, - isSingleColumn = true, link, showPerPageOptions = true, }: ServiceOverviewDependenciesTableProps) { @@ -136,7 +134,6 @@ export function ServiceOverviewDependenciesTable({ - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/x-pack/plugins/apm/public/components/routing/home/index.tsx b/x-pack/plugins/apm/public/components/routing/home/index.tsx index 8a8b5d789f1cf..3512a44acab45 100644 --- a/x-pack/plugins/apm/public/components/routing/home/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/index.tsx @@ -21,33 +21,40 @@ import { TraceOverview } from '../../app/trace_overview'; import { TraceExplorer } from '../../app/trace_explorer'; import { TopTracesOverview } from '../../app/top_traces_overview'; import { ApmMainTemplate } from '../templates/apm_main_template'; -import { RedirectToBackendOverviewRouteView } from './redirect_to_backend_overview_route_view'; import { ServiceGroupTemplate } from '../templates/service_group_template'; import { ServiceGroupsRedirect } from '../service_groups_redirect'; import { RedirectTo } from '../redirect_to'; import { offsetRt } from '../../../../common/comparison_rt'; import { TransactionTab } from '../../app/transaction_details/waterfall_with_summary/transaction_tabs'; +import { BackendDetailOperations } from '../../app/backend_detail_operations'; +import { BackendDetailView } from '../../app/backend_detail_view'; +import { RedirectPathBackendDetailView } from './redirect_path_backend_detail_view'; +import { RedirectBackendsToBackendDetailOverview } from './redirect_backends_to_backend_detail_view'; function page< TPath extends string, - TChildren extends Record | undefined = undefined + TChildren extends Record | undefined = undefined, + TParams extends t.Type | undefined = undefined >({ path, element, children, title, showServiceGroupSaveButton = false, + params, }: { path: TPath; element: React.ReactElement; children?: TChildren; title: string; showServiceGroupSaveButton?: boolean; + params?: TParams; }): Record< TPath, { element: React.ReactElement; - } & (TChildren extends Record ? { children: TChildren } : {}) + } & (TChildren extends Record ? { children: TChildren } : {}) & + (TParams extends t.Type ? { params: TParams } : {}) > { return { [path]: { @@ -133,6 +140,13 @@ export const DependenciesInventoryTitle = i18n.translate( } ); +export const DependenciesOperationsTitle = i18n.translate( + 'xpack.apm.views.dependenciesOperations.title', + { + defaultMessage: 'Operations', + } +); + export const home = { '/': { element: , @@ -217,38 +231,67 @@ export const home = { }, }, }), + ...page({ + path: '/backends/inventory', + title: DependenciesInventoryTitle, + element: , + params: t.partial({ + query: t.intersection([ + t.type({ + comparisonEnabled: toBooleanRt, + }), + offsetRt, + ]), + }), + }), + '/backends/{backendName}/overview': { + element: , + params: t.type({ + path: t.type({ + backendName: t.string, + }), + }), + }, '/backends': { element: , params: t.partial({ query: t.intersection([ t.type({ comparisonEnabled: toBooleanRt, + backendName: t.string, }), offsetRt, ]), }), children: { - '/backends/{backendName}/overview': { - element: , - params: t.type({ - path: t.type({ - backendName: t.string, - }), - }), - }, - '/backends/overview': { - element: , - params: t.type({ - query: t.type({ - backendName: t.string, - }), - }), + '/backends': { + element: ( + + + + ), + children: { + '/backends/operations': { + element: , + children: { + '/backends/operation': { + params: t.type({ + query: t.type({ + spanName: t.string, + }), + }), + element: , + }, + }, + }, + '/backends/overview': { + element: , + }, + '/backends': { + element: , + }, + }, }, - ...page({ - path: '/backends', - title: DependenciesInventoryTitle, - element: , - }), }, }, '/': { diff --git a/x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_detail_view.tsx b/x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_detail_view.tsx new file mode 100644 index 0000000000000..34dfde53d210c --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_detail_view.tsx @@ -0,0 +1,19 @@ +/* + * 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 qs from 'query-string'; +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useApmParams } from '../../../hooks/use_apm_params'; + +export function RedirectBackendsToBackendDetailOverview() { + const { query } = useApmParams('/backends'); + + const search = qs.stringify(query); + + return ; +} diff --git a/x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_inventory.tsx b/x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_inventory.tsx new file mode 100644 index 0000000000000..654b63a310d2f --- /dev/null +++ b/x-pack/plugins/apm/public/components/routing/home/redirect_backends_to_backend_inventory.tsx @@ -0,0 +1,34 @@ +/* + * 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 { useLocation, Redirect } from 'react-router-dom'; +import qs from 'query-string'; +import React from 'react'; + +export function RedirectBackendsToBackendInventory({ + children, +}: { + children: React.ReactElement; +}) { + const location = useLocation(); + + const query = qs.parse(location.search); + + const normalizedPathname = location.pathname.replace(/\/$/, ''); + if (normalizedPathname === '/backends' && !('backendName' in query)) { + return ( + + ); + } + + return children; +} diff --git a/x-pack/plugins/apm/public/components/routing/home/redirect_to_backend_overview_route_view.tsx b/x-pack/plugins/apm/public/components/routing/home/redirect_path_backend_detail_view.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/routing/home/redirect_to_backend_overview_route_view.tsx rename to x-pack/plugins/apm/public/components/routing/home/redirect_path_backend_detail_view.tsx index ef6d04828c19d..757ab0fff6cec 100644 --- a/x-pack/plugins/apm/public/components/routing/home/redirect_to_backend_overview_route_view.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/redirect_path_backend_detail_view.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { Redirect } from 'react-router-dom'; import { useApmParams } from '../../../hooks/use_apm_params'; -export function RedirectToBackendOverviewRouteView() { +export function RedirectPathBackendDetailView() { const { path: { backendName }, query, diff --git a/x-pack/plugins/apm/public/components/routing/templates/backend_detail_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/backend_detail_template.tsx index ac3d576019384..e36768b8e4482 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/backend_detail_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/backend_detail_template.tsx @@ -7,24 +7,45 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import React from 'react'; +import { i18n } from '@kbn/i18n'; import { ApmMainTemplate } from './apm_main_template'; import { SpanIcon } from '../../shared/span_icon'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; import { useFetcher } from '../../../hooks/use_fetcher'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { useApmRoutePath } from '../../../hooks/use_apm_route_path'; +import { SearchBar } from '../../shared/search_bar'; +import { + getKueryBarBoolFilter, + kueryBarPlaceholder, +} from '../../../../common/backends'; +import { useOperationBreakdownEnabledSetting } from '../../../hooks/use_operations_breakdown_enabled_setting'; interface Props { - title: string; children: React.ReactNode; } -export function BackendDetailTemplate({ title, children }: Props) { +export function BackendDetailTemplate({ children }: Props) { const { - query: { backendName, rangeFrom, rangeTo }, - } = useApmParams('/backends/overview'); + query, + query: { backendName, rangeFrom, rangeTo, environment }, + } = useApmParams('/backends'); + + const router = useApmRouter(); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const path = useApmRoutePath(); + + const isOperationsBreakdownFeatureEnabled = + useOperationBreakdownEnabledSetting(); + + const kueryBarBoolFilter = getKueryBarBoolFilter({ + environment, + backendName, + }); + const backendMetadataFetch = useFetcher( (callApmApi) => { if (!start || !end) { @@ -46,9 +67,35 @@ export function BackendDetailTemplate({ title, children }: Props) { const { data: { metadata } = {} } = backendMetadataFetch; + const tabs = isOperationsBreakdownFeatureEnabled + ? [ + { + key: 'overview', + href: router.link('/backends/overview', { + query, + }), + label: i18n.translate('xpack.apm.backendDetailOverview.title', { + defaultMessage: 'Overview', + }), + isSelected: path === '/backends/overview', + }, + { + key: 'operations', + href: router.link('/backends/operations', { + query, + }), + label: i18n.translate('xpack.apm.backendDetailOperations.title', { + defaultMessage: 'Operations', + }), + isSelected: path === '/backends/operations', + }, + ] + : []; + return ( @@ -66,6 +113,11 @@ export function BackendDetailTemplate({ title, children }: Props) { ), }} > + {children} ); diff --git a/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx b/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx new file mode 100644 index 0000000000000..49d9d342947f9 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx @@ -0,0 +1,215 @@ +/* + * 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 React from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiToolTip, + RIGHT_ALIGNMENT, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Breakpoints } from '../../../hooks/use_breakpoints'; +import { + ChartType, + getTimeSeriesColor, +} from '../charts/helper/get_timeseries_color'; +import { ListMetric } from '../list_metric'; +import { ITableColumn } from '../managed_table'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { + asMillisecondDuration, + asPercent, + asTransactionRate, +} from '../../../../common/utils/formatters'; +import { Coordinate } from '../../../../typings/timeseries'; +import { ImpactBar } from '../impact_bar'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; + +export interface SpanMetricGroup { + latency: number | null; + throughput: number | null; + failureRate: number | null; + impact: number | null; + currentStats: + | { + latency: Coordinate[]; + throughput: Coordinate[]; + failureRate: Coordinate[]; + } + | undefined; + previousStats: + | { + latency: Coordinate[]; + throughput: Coordinate[]; + failureRate: Coordinate[]; + impact: number; + } + | undefined; +} + +export function getSpanMetricColumns({ + breakpoints, + comparisonFetchStatus, +}: { + breakpoints: Breakpoints; + comparisonFetchStatus: FETCH_STATUS; +}): Array> { + const { isLarge } = breakpoints; + const shouldShowSparkPlots = !isLarge; + const isLoading = + comparisonFetchStatus === FETCH_STATUS.LOADING || + comparisonFetchStatus === FETCH_STATUS.NOT_INITIATED; + return [ + { + field: 'latency', + name: i18n.translate('xpack.apm.dependenciesTable.columnLatency', { + defaultMessage: 'Latency (avg.)', + }), + align: RIGHT_ALIGNMENT, + render: (_, { latency, currentStats, previousStats }) => { + const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( + ChartType.LATENCY_AVG + ); + + return ( + + ); + }, + sortable: true, + }, + { + field: 'throughput', + name: i18n.translate('xpack.apm.dependenciesTable.columnThroughput', { + defaultMessage: 'Throughput', + }), + align: RIGHT_ALIGNMENT, + render: (_, { throughput, currentStats, previousStats }) => { + const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( + ChartType.THROUGHPUT + ); + + return ( + + ); + }, + sortable: true, + }, + { + field: 'failureRate', + name: ( + + <> + {i18n.translate('xpack.apm.dependenciesTable.columnErrorRate', { + defaultMessage: 'Failed transaction rate', + })} +   + + + + ), + align: RIGHT_ALIGNMENT, + render: (_, { failureRate, currentStats, previousStats }) => { + const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( + ChartType.FAILED_TRANSACTION_RATE + ); + + return ( + + ); + }, + sortable: true, + }, + { + field: 'impact', + name: ( + + <> + {i18n.translate('xpack.apm.dependenciesTable.columnImpact', { + defaultMessage: 'Impact', + })} +   + + + + ), + align: RIGHT_ALIGNMENT, + render: (_, { impact, previousStats }) => { + return ( + + + + + {previousStats && isFiniteNumber(previousStats.impact) && ( + + + + )} + + ); + }, + sortable: true, + }, + ]; +} diff --git a/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx index a0dba6f5b870b..99f50def4d417 100644 --- a/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx @@ -5,34 +5,20 @@ * 2.0. */ -import { - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiToolTip, - EuiIcon, - RIGHT_ALIGNMENT, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { ConnectionStatsItemWithComparisonData } from '../../../../common/connections'; -import { - asMillisecondDuration, - asPercent, - asTransactionRate, -} from '../../../../common/utils/formatters'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { EmptyMessage } from '../empty_message'; -import { ImpactBar } from '../impact_bar'; -import { ListMetric } from '../list_metric'; import { ITableColumn, ManagedTable } from '../managed_table'; import { OverviewTableContainer } from '../overview_table_container'; import { TruncateWithTooltip } from '../truncate_with_tooltip'; import { - ChartType, - getTimeSeriesColor, -} from '../charts/helper/get_timeseries_color'; + getSpanMetricColumns, + SpanMetricGroup, +} from './get_span_metric_columns'; export type DependenciesItem = Omit< ConnectionStatsItemWithComparisonData, @@ -45,7 +31,6 @@ export type DependenciesItem = Omit< interface Props { dependencies: DependenciesItem[]; fixedHeight?: boolean; - isSingleColumn?: boolean; link?: React.ReactNode; title: React.ReactNode; nameColumnTitle: React.ReactNode; @@ -54,11 +39,15 @@ interface Props { showPerPageOptions?: boolean; } +type FormattedSpanMetricGroup = SpanMetricGroup & { + name: string; + link: React.ReactElement; +}; + export function DependenciesTable(props: Props) { const { dependencies, fixedHeight, - isSingleColumn = true, link, title, nameColumnTitle, @@ -68,10 +57,31 @@ export function DependenciesTable(props: Props) { } = props; // SparkPlots should be hidden if we're in two-column view and size XL (1200px) - const { isXl } = useBreakpoints(); - const shouldShowSparkPlots = isSingleColumn || !isXl; + const breakpoints = useBreakpoints(); + + const items: FormattedSpanMetricGroup[] = dependencies.map((dependency) => ({ + name: dependency.name, + link: dependency.link, + latency: dependency.currentStats.latency.value, + throughput: dependency.currentStats.throughput.value, + failureRate: dependency.currentStats.errorRate.value, + impact: dependency.currentStats.impact, + currentStats: { + latency: dependency.currentStats.latency.timeseries, + throughput: dependency.currentStats.throughput.timeseries, + failureRate: dependency.currentStats.errorRate.timeseries, + }, + previousStats: dependency.previousStats + ? { + latency: dependency.previousStats.latency.timeseries, + throughput: dependency.previousStats.throughput.timeseries, + failureRate: dependency.previousStats.errorRate.timeseries, + impact: dependency.previousStats.impact, + } + : undefined, + })); - const columns: Array> = [ + const columns: Array> = [ { field: 'name', name: nameColumnTitle, @@ -81,173 +91,12 @@ export function DependenciesTable(props: Props) { }, sortable: true, }, - { - field: 'latencyValue', - name: i18n.translate('xpack.apm.dependenciesTable.columnLatency', { - defaultMessage: 'Latency (avg.)', - }), - align: RIGHT_ALIGNMENT, - render: (_, { currentStats, previousStats }) => { - const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( - ChartType.LATENCY_AVG - ); - - return ( - - ); - }, - sortable: true, - }, - { - field: 'throughputValue', - name: i18n.translate('xpack.apm.dependenciesTable.columnThroughput', { - defaultMessage: 'Throughput', - }), - align: RIGHT_ALIGNMENT, - render: (_, { currentStats, previousStats }) => { - const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( - ChartType.THROUGHPUT - ); - - return ( - - ); - }, - sortable: true, - }, - { - field: 'errorRateValue', - name: ( - - <> - {i18n.translate('xpack.apm.dependenciesTable.columnErrorRate', { - defaultMessage: 'Failed transaction rate', - })} -   - - - - ), - align: RIGHT_ALIGNMENT, - render: (_, { currentStats, previousStats }) => { - const { currentPeriodColor, previousPeriodColor } = getTimeSeriesColor( - ChartType.FAILED_TRANSACTION_RATE - ); - - return ( - - ); - }, - sortable: true, - }, - { - field: 'impactValue', - name: ( - - <> - {i18n.translate('xpack.apm.dependenciesTable.columnImpact', { - defaultMessage: 'Impact', - })} -   - - - - ), - align: RIGHT_ALIGNMENT, - render: (_, { currentStats, previousStats }) => { - return ( - - - - - {previousStats?.impact !== undefined && ( - - - - )} - - ); - }, - sortable: true, - }, + ...getSpanMetricColumns({ + breakpoints, + comparisonFetchStatus: status, + }), ]; - // need top-level sortable fields for the managed table - const items = - dependencies.map((item) => ({ - ...item, - errorRateValue: item.currentStats.errorRate.value, - latencyValue: item.currentStats.latency.value, - throughputValue: item.currentStats.throughput.value, - impactValue: item.currentStats.impact, - })) ?? []; - const noItemsMessage = !compact ? ( (apmOperationsTab, false); +} diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 1ce36ac949a41..9edc7fe6f5d28 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -178,7 +178,7 @@ export class ApmPlugin implements Plugin { { label: dependenciesTitle, app: 'apm', - path: '/backends', + path: '/backends/inventory', onClick: () => { const { usageCollection } = pluginsStart as { usageCollection?: UsageCollectionStart; @@ -298,7 +298,11 @@ export class ApmPlugin implements Plugin { }, { id: 'traces', title: tracesTitle, path: '/traces' }, { id: 'service-map', title: serviceMapTitle, path: '/service-map' }, - { id: 'backends', title: dependenciesTitle, path: '/backends' }, + { + id: 'backends', + title: dependenciesTitle, + path: '/backends/inventory', + }, ], async mount(appMountParameters: AppMountParameters) { diff --git a/x-pack/plugins/apm/server/routes/backends/get_top_backend_operations.ts b/x-pack/plugins/apm/server/routes/backends/get_top_backend_operations.ts new file mode 100644 index 0000000000000..d7e163bb1686e --- /dev/null +++ b/x-pack/plugins/apm/server/routes/backends/get_top_backend_operations.ts @@ -0,0 +1,192 @@ +/* + * 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 { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { + EVENT_OUTCOME, + SPAN_DESTINATION_SERVICE_RESOURCE, + SPAN_DURATION, + SPAN_NAME, +} from '../../../common/elasticsearch_fieldnames'; +import { Environment } from '../../../common/environment_rt'; +import { EventOutcome } from '../../../common/event_outcome'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { environmentQuery } from '../../../common/utils/environment_query'; +import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { + calculateThroughputWithInterval, + calculateThroughputWithRange, +} from '../../lib/helpers/calculate_throughput'; +import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; +import { Setup } from '../../lib/helpers/setup_request'; +import { calculateImpactBuilder } from '../traces/calculate_impact_builder'; + +const MAX_NUM_OPERATIONS = 500; + +export interface BackendOperation { + spanName: string; + latency: number | null; + throughput: number; + failureRate: number | null; + impact: number; + timeseries: Record< + 'latency' | 'throughput' | 'failureRate', + Array<{ x: number; y: number | null }> + >; +} + +export async function getTopBackendOperations({ + setup, + backendName, + start, + end, + offset, + environment, + kuery, +}: { + setup: Setup; + backendName: string; + start: number; + end: number; + offset?: string; + environment: Environment; + kuery: string; +}) { + const { apmEventClient } = setup; + + const { startWithOffset, endWithOffset, offsetInMs } = getOffsetInMs({ + start, + end, + offset, + }); + + const aggs = { + duration: { + avg: { + field: SPAN_DURATION, + }, + }, + successful: { + filter: { + term: { + [EVENT_OUTCOME]: EventOutcome.success, + }, + }, + }, + failure: { + filter: { + term: { + [EVENT_OUTCOME]: EventOutcome.failure, + }, + }, + }, + }; + + const response = await apmEventClient.search('get_top_backend_operations', { + apm: { + events: [ProcessorEvent.span], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(SPAN_DESTINATION_SERVICE_RESOURCE, backendName), + ], + }, + }, + aggs: { + operationName: { + terms: { + field: SPAN_NAME, + size: MAX_NUM_OPERATIONS, + }, + aggs: { + over_time: { + date_histogram: getMetricsDateHistogramParams({ + start: startWithOffset, + end: endWithOffset, + metricsInterval: 60, + }), + aggs, + }, + ...aggs, + total_time: { + sum: { + field: SPAN_DURATION, + }, + }, + }, + }, + }, + }, + }); + + const getImpact = calculateImpactBuilder( + response.aggregations?.operationName.buckets.map( + (bucket) => bucket.total_time.value + ) ?? [] + ); + + return ( + response.aggregations?.operationName.buckets.map( + (bucket): BackendOperation => { + const timeseries: BackendOperation['timeseries'] = { + latency: [], + throughput: [], + failureRate: [], + }; + + bucket.over_time.buckets.forEach((dateBucket) => { + const x = dateBucket.key + offsetInMs; + timeseries.throughput.push({ + x, + y: calculateThroughputWithInterval({ + value: dateBucket.doc_count, + bucketSize: 60, + }), + }); + timeseries.latency.push({ x, y: dateBucket.duration.value }); + timeseries.failureRate.push({ + x, + y: + dateBucket.failure.doc_count > 0 || + dateBucket.successful.doc_count > 0 + ? dateBucket.failure.doc_count / + (dateBucket.successful.doc_count + + dateBucket.failure.doc_count) + : null, + }); + }); + + return { + spanName: bucket.key as string, + latency: bucket.duration.value, + throughput: calculateThroughputWithRange({ + start: startWithOffset, + end: endWithOffset, + value: bucket.doc_count, + }), + failureRate: + bucket.failure.doc_count > 0 || bucket.successful.doc_count > 0 + ? bucket.failure.doc_count / + (bucket.successful.doc_count + bucket.failure.doc_count) + : null, + impact: getImpact(bucket.total_time.value ?? 0), + timeseries, + }; + } + ) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/backends/route.ts b/x-pack/plugins/apm/server/routes/backends/route.ts index 98fde4ad83804..120f9274a5127 100644 --- a/x-pack/plugins/apm/server/routes/backends/route.ts +++ b/x-pack/plugins/apm/server/routes/backends/route.ts @@ -18,6 +18,10 @@ import { getThroughputChartsForBackend } from './get_throughput_charts_for_backe import { getErrorRateChartsForBackend } from './get_error_rate_charts_for_backend'; import { ConnectionStatsItemWithImpact } from '../../../common/connections'; import { offsetRt } from '../../../common/comparison_rt'; +import { + BackendOperation, + getTopBackendOperations, +} from './get_top_backend_operations'; const topBackendsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/backends/top_backends', @@ -423,6 +427,41 @@ const backendFailedTransactionRateChartsRoute = createApmServerRoute({ }, }); +const backendOperationsRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/backends/operations', + options: { + tags: ['access:apm'], + }, + params: t.type({ + query: t.intersection([ + rangeRt, + environmentRt, + kueryRt, + offsetRt, + t.type({ backendName: t.string }), + ]), + }), + handler: async (resources): Promise<{ operations: BackendOperation[] }> => { + const setup = await setupRequest(resources); + + const { + query: { backendName, start, end, environment, kuery, offset }, + } = resources.params; + + const operations = await getTopBackendOperations({ + setup, + backendName, + start, + end, + offset, + environment, + kuery, + }); + + return { operations }; + }, +}); + export const backendsRouteRepository = { ...topBackendsRoute, ...upstreamServicesForBackendRoute, @@ -430,4 +469,5 @@ export const backendsRouteRepository = { ...backendLatencyChartsRoute, ...backendThroughputChartsRoute, ...backendFailedTransactionRateChartsRoute, + ...backendOperationsRoute, }; diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index b380fa6c80745..88134d04586d6 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -18,6 +18,7 @@ export { apmServiceInventoryOptimizedSorting, apmProgressiveLoading, apmTraceExplorerTab, + apmOperationsTab, } from './ui_settings_keys'; export { diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index c49f95053bb8a..17a5be74c4b87 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -16,3 +16,4 @@ export const enableServiceGroups = 'observability:enableServiceGroups'; export const apmServiceInventoryOptimizedSorting = 'observability:apmServiceInventoryOptimizedSorting'; export const apmTraceExplorerTab = 'observability:apmTraceExplorerTab'; +export const apmOperationsTab = 'observability:apmOperationsTab'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 67a7a6bf0bd54..827a0205512e0 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -20,6 +20,7 @@ import { apmServiceInventoryOptimizedSorting, enableNewSyntheticsView, apmTraceExplorerTab, + apmOperationsTab, } from '../common/ui_settings_keys'; const technicalPreviewLabel = i18n.translate( @@ -203,4 +204,19 @@ export const uiSettings: Record[${technicalPreviewLabel}]` }, + }), + schema: schema.boolean(), + value: false, + requiresPageReload: true, + type: 'boolean', + }, }; diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts index b587cdd99b1c9..53ff1b47981e6 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/top_dependencies.spec.ts @@ -53,7 +53,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Top dependencies', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { - describe.skip('when data is generated', () => { + describe('when data is generated', () => { let topDependencies: TopDependencies; before(async () => { diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts new file mode 100644 index 0000000000000..e7b73a40ede83 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/dependencies/top_operations.spec.ts @@ -0,0 +1,271 @@ +/* + * 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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { apm, timerange } from '@elastic/apm-synthtrace'; +import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; +import { ValuesType } from 'utility-types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { roundNumber } from '../../utils'; + +type TopOperations = APIReturnType<'GET /internal/apm/backends/operations'>['operations']; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi({ + backendName, + environment = ENVIRONMENT_ALL.value, + kuery = '', + }: { + backendName: string; + environment?: string; + kuery?: string; + }) { + return await apmApiClient + .readUser({ + endpoint: 'GET /internal/apm/backends/operations', + params: { + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment, + kuery, + backendName, + }, + }, + }) + .then(({ body }) => body.operations); + } + + const ES_SEARCH_DURATION = 100; + const ES_SEARCH_UNKNOWN_RATE = 5; + const ES_SEARCH_SUCCESS_RATE = 4; + const ES_SEARCH_FAILURE_RATE = 1; + + const ES_BULK_RATE = 20; + const ES_BULK_DURATION = 1000; + + const REDIS_SET_RATE = 10; + const REDIS_SET_DURATION = 10; + + async function generateData() { + const synthGoInstance = apm.service('synth-go', 'production', 'go').instance('instance-a'); + const synthJavaInstance = apm + .service('synth-java', 'development', 'java') + .instance('instance-a'); + + const interval = timerange(start, end).interval('1m'); + + return await synthtraceEsClient.index([ + interval + .rate(ES_SEARCH_UNKNOWN_RATE) + .generator((timestamp) => + synthGoInstance + .span('/_search', 'db', 'elasticsearch') + .destination('elasticsearch') + .timestamp(timestamp) + .duration(ES_SEARCH_DURATION) + ), + interval + .rate(ES_SEARCH_SUCCESS_RATE) + .generator((timestamp) => + synthGoInstance + .span('/_search', 'db', 'elasticsearch') + .destination('elasticsearch') + .timestamp(timestamp) + .success() + .duration(ES_SEARCH_DURATION) + ), + interval + .rate(ES_SEARCH_FAILURE_RATE) + .generator((timestamp) => + synthGoInstance + .span('/_search', 'db', 'elasticsearch') + .destination('elasticsearch') + .timestamp(timestamp) + .failure() + .duration(ES_SEARCH_DURATION) + ), + interval + .rate(ES_BULK_RATE) + .generator((timestamp) => + synthJavaInstance + .span('/_bulk', 'db', 'elasticsearch') + .destination('elasticsearch') + .timestamp(timestamp) + .duration(ES_BULK_DURATION) + ), + interval + .rate(REDIS_SET_RATE) + .generator((timestamp) => + synthJavaInstance + .span('SET', 'db', 'redis') + .destination('redis') + .timestamp(timestamp) + .duration(REDIS_SET_DURATION) + ), + ]); + } + + registry.when('Top operations when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles empty state', async () => { + const operations = await callApi({ backendName: 'elasticsearch' }); + expect(operations).to.empty(); + }); + }); + + registry.when( + 'Top operations when data is generated', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + before(() => generateData()); + + after(() => synthtraceEsClient.clean()); + + describe('requested for elasticsearch', () => { + let response: TopOperations; + let searchOperation: ValuesType; + let bulkOperation: ValuesType; + + before(async () => { + response = await callApi({ backendName: 'elasticsearch' }); + searchOperation = response.find((op) => op.spanName === '/_search')!; + bulkOperation = response.find((op) => op.spanName === '/_bulk')!; + }); + + it('returns the correct operations', () => { + expect(response.length).to.eql(2); + + expect(searchOperation).to.be.ok(); + expect(bulkOperation).to.be.ok(); + }); + + it('returns the correct latency', () => { + expect(searchOperation.latency).to.eql(ES_SEARCH_DURATION * 1000); + expect(bulkOperation.latency).to.eql(ES_BULK_DURATION * 1000); + }); + + it('returns the correct throughput', () => { + const expectedSearchThroughput = roundNumber( + ES_SEARCH_UNKNOWN_RATE + ES_SEARCH_SUCCESS_RATE + ES_SEARCH_FAILURE_RATE + ); + const expectedBulkThroughput = ES_BULK_RATE; + + expect(roundNumber(searchOperation.throughput)).to.eql(expectedSearchThroughput); + expect(roundNumber(bulkOperation.throughput)).to.eql(expectedBulkThroughput); + + expect( + searchOperation.timeseries.throughput + .map((bucket) => bucket.y) + .every((val) => val === expectedSearchThroughput) + ); + }); + + it('returns the correct failure rate', () => { + const expectedSearchFailureRate = + ES_SEARCH_FAILURE_RATE / (ES_SEARCH_SUCCESS_RATE + ES_SEARCH_FAILURE_RATE); + const expectedBulkFailureRate = null; + + expect(searchOperation.failureRate).to.be(expectedSearchFailureRate); + + expect(bulkOperation.failureRate).to.be(expectedBulkFailureRate); + + expect( + searchOperation.timeseries.failureRate + .map((bucket) => bucket.y) + .every((val) => val === expectedSearchFailureRate) + ); + + expect( + bulkOperation.timeseries.failureRate + .map((bucket) => bucket.y) + .every((val) => val === expectedBulkFailureRate) + ); + }); + + it('returns the correct impact', () => { + expect(searchOperation.impact).to.eql(0); + expect(bulkOperation.impact).to.eql(100); + }); + }); + + describe('requested for redis', () => { + let response: TopOperations; + let setOperation: ValuesType; + + before(async () => { + response = await callApi({ backendName: 'redis' }); + setOperation = response.find((op) => op.spanName === 'SET')!; + }); + + it('returns the correct operations', () => { + expect(response.length).to.eql(1); + + expect(setOperation).to.be.ok(); + }); + + it('returns the correct latency', () => { + expect(setOperation.latency).to.eql(REDIS_SET_DURATION * 1000); + }); + + it('returns the correct throughput', () => { + expect(roundNumber(setOperation.throughput)).to.eql(roundNumber(REDIS_SET_RATE)); + }); + }); + + describe('requested for a specific service', () => { + let response: TopOperations; + let searchOperation: ValuesType; + let bulkOperation: ValuesType | undefined; + + before(async () => { + response = await callApi({ + backendName: 'elasticsearch', + kuery: `service.name:"synth-go"`, + }); + searchOperation = response.find((op) => op.spanName === '/_search')!; + bulkOperation = response.find((op) => op.spanName === '/_bulk'); + }); + + it('returns the correct operations', () => { + expect(response.length).to.eql(1); + + expect(searchOperation).to.be.ok(); + expect(bulkOperation).not.to.be.ok(); + }); + }); + + describe('requested for a specific environment', () => { + let response: TopOperations; + let searchOperation: ValuesType | undefined; + let bulkOperation: ValuesType; + + before(async () => { + response = await callApi({ + backendName: 'elasticsearch', + environment: 'development', + }); + searchOperation = response.find((op) => op.spanName === '/_search'); + bulkOperation = response.find((op) => op.spanName === '/_bulk')!; + }); + + it('returns the correct operations', () => { + expect(response.length).to.eql(1); + + expect(searchOperation).not.to.be.ok(); + expect(bulkOperation).to.be.ok(); + }); + }); + } + ); +} From c495b146461b971a4cf08d19800686057ef4b4a8 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Thu, 9 Jun 2022 11:08:34 +0200 Subject: [PATCH 23/44] Delete 'usersEnabled' feature flag (#131926) * Delete 'usersEnabled' feature flag * Move HostsKpiAuthentications to user folder --- .../security_solution/common/constants.ts | 1 - .../common/experimental_features.ts | 1 - .../security_solution/hosts/kpi/index.ts | 7 +- .../security_solution/index.ts | 16 +-- .../security_solution/users/index.ts | 1 + .../kpi/authentications/index.ts | 13 ++- .../cypress/urls/navigation.ts | 1 - .../security_solution/cypress/urls/state.ts | 4 +- .../public/app/deep_links/index.test.ts | 30 ++---- .../public/app/deep_links/index.ts | 9 -- .../authentications_host_table.tsx | 7 +- .../components/authentication/helpers.tsx | 18 ++-- .../navigation/tab_navigation/index.test.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 10 ++ .../index.tsx | 1 - .../kpi_user_authentication_metric_failure.ts | 0 .../kpi_user_authentications_area.ts | 0 .../kpi_user_authentications_bar.ts | 0 ...kpi_user_authentications_metric_success.ts | 0 .../common/lib/cell_actions/field_value.tsx | 7 +- .../common/lib/cell_actions/helpers.tsx | 23 ++-- .../public/common/links/links.test.ts | 25 ++++- .../preview_table_cell_renderer.tsx | 10 +- .../hosts/components/kpi_hosts/index.tsx | 30 ------ .../hosts/components/kpi_hosts/types.ts | 2 - .../kpi_hosts/authentications/translations.ts | 22 ---- .../security_solution/public/hosts/links.ts | 8 -- .../public/hosts/pages/hosts.tsx | 2 - .../public/hosts/pages/hosts_tabs.tsx | 4 - .../public/hosts/pages/index.tsx | 1 - .../public/hosts/pages/nav_tabs.test.tsx | 32 ------ .../public/hosts/pages/nav_tabs.tsx | 12 --- .../public/hosts/store/reducer.ts | 2 +- .../risk_score/containers/kpi/index.tsx | 16 +-- .../body/renderers/formatted_field.tsx | 4 +- .../cell_rendering/default_cell_renderer.tsx | 10 +- .../kpi_users}/authentications/index.test.tsx | 14 +-- .../kpi_users}/authentications/index.tsx | 31 +++--- .../authentications/translations.ts | 0 .../users/components/kpi_users/index.tsx | 4 +- .../kpi_users/total_users/index.tsx | 14 +-- .../users}/authentications/index.test.tsx | 6 +- .../users}/authentications/index.tsx | 48 ++++----- .../users/authentications/translations.ts | 22 ++++ .../security_solution/public/users/links.ts | 1 - .../factory/hosts/index.test.ts | 3 - .../security_solution/factory/hosts/index.ts | 2 - .../hosts/kpi/authentications/helpers.ts | 22 ---- .../hosts/kpi/authentications/index.ts | 65 ----------- .../factory/hosts/kpi/index.ts | 1 - .../security_solution/factory/users/index.ts | 2 + .../users/kpi/authentications/index.ts | 60 +++++++++++ .../query.users_kpi_authentications.dsl.ts} | 6 +- .../translations/translations/fr-FR.json | 3 - .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../apis/security_solution/kpi_hosts.ts | 53 --------- .../apis/security_solution/kpi_users.ts | 101 ++++++++++++++++++ 58 files changed, 337 insertions(+), 458 deletions(-) rename x-pack/plugins/security_solution/common/search_strategy/security_solution/{hosts => users}/kpi/authentications/index.ts (56%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => users}/kpi_user_authentication_metric_failure.ts (100%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => users}/kpi_user_authentications_area.ts (100%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => users}/kpi_user_authentications_bar.ts (100%) rename x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/{hosts => users}/kpi_user_authentications_metric_success.ts (100%) delete mode 100644 x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts rename x-pack/plugins/security_solution/public/{hosts/components/kpi_hosts => users/components/kpi_users}/authentications/index.test.tsx (80%) rename x-pack/plugins/security_solution/public/{hosts/components/kpi_hosts => users/components/kpi_users}/authentications/index.tsx (72%) rename x-pack/plugins/security_solution/public/{hosts/components/kpi_hosts => users/components/kpi_users}/authentications/translations.ts (100%) rename x-pack/plugins/security_solution/public/{hosts/containers/kpi_hosts => users/containers/users}/authentications/index.test.tsx (84%) rename x-pack/plugins/security_solution/public/{hosts/containers/kpi_hosts => users/containers/users}/authentications/index.tsx (77%) create mode 100644 x-pack/plugins/security_solution/public/users/containers/users/authentications/translations.ts delete mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts delete mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/index.ts rename x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/{hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts => users/kpi/authentications/query.users_kpi_authentications.dsl.ts} (91%) create mode 100644 x-pack/test/api_integration/apis/security_solution/kpi_users.ts diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 9726aa92fe5f9..b48665e7f9d58 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -95,7 +95,6 @@ export enum SecurityPageName { hostsExternalAlerts = 'hosts-external_alerts', hostsRisk = 'hosts-risk', hostsEvents = 'hosts-events', - hostsAuthentications = 'hosts-authentications', investigate = 'investigate', landing = 'get_started', network = 'network', diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 60b408ffee699..8ba1a611af2b6 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -15,7 +15,6 @@ export const allowedExperimentalValues = Object.freeze({ tGridEnabled: true, tGridEventRenderedViewEnabled: true, excludePoliciesInFilterEnabled: false, - usersEnabled: true, kubernetesEnabled: false, disableIsolationUIPendingStatuses: false, riskyHostsEnabled: false, diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts index 79054fc736a80..94da316f3b0ae 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts @@ -5,22 +5,21 @@ * 2.0. */ -export * from './authentications'; +export * from '../../users/kpi/authentications'; export * from './common'; export * from './hosts'; export * from './unique_ips'; -import { HostsKpiAuthenticationsStrategyResponse } from './authentications'; +import { UsersKpiAuthenticationsStrategyResponse } from '../../users/kpi/authentications'; import { HostsKpiHostsStrategyResponse } from './hosts'; import { HostsKpiUniqueIpsStrategyResponse } from './unique_ips'; export enum HostsKpiQueries { - kpiAuthentications = 'hostsKpiAuthentications', kpiHosts = 'hostsKpiHosts', kpiUniqueIps = 'hostsKpiUniqueIps', } export type HostsKpiStrategyResponse = - | Omit + | Omit | Omit | Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index c9d97a704589a..1d539daabc011 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -19,8 +19,6 @@ import { HostsUncommonProcessesStrategyResponse, HostsUncommonProcessesRequestOptions, HostsKpiQueries, - HostsKpiAuthenticationsStrategyResponse, - HostsKpiAuthenticationsRequestOptions, HostsKpiHostsStrategyResponse, HostsKpiHostsRequestOptions, HostsKpiUniqueIpsStrategyResponse, @@ -84,6 +82,12 @@ import { TotalUsersKpiRequestOptions, TotalUsersKpiStrategyResponse, } from './users/kpi/total_users'; + +import { + UsersKpiAuthenticationsRequestOptions, + UsersKpiAuthenticationsStrategyResponse, +} from './users/kpi/authentications'; + import { UsersRequestOptions, UsersStrategyResponse } from './users/all'; import { UserAuthenticationsRequestOptions, @@ -137,8 +141,6 @@ export type StrategyResponseType = T extends HostsQ ? HostFirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesStrategyResponse - : T extends HostsKpiQueries.kpiAuthentications - ? HostsKpiAuthenticationsStrategyResponse : T extends HostsKpiQueries.kpiHosts ? HostsKpiHostsStrategyResponse : T extends HostsKpiQueries.kpiUniqueIps @@ -151,6 +153,8 @@ export type StrategyResponseType = T extends HostsQ ? UserAuthenticationsStrategyResponse : T extends UsersQueries.users ? UsersStrategyResponse + : T extends UsersQueries.kpiAuthentications + ? UsersKpiAuthenticationsStrategyResponse : T extends NetworkQueries.details ? NetworkDetailsStrategyResponse : T extends NetworkQueries.dns @@ -199,8 +203,6 @@ export type StrategyRequestType = T extends HostsQu ? HostFirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesRequestOptions - : T extends HostsKpiQueries.kpiAuthentications - ? HostsKpiAuthenticationsRequestOptions : T extends HostsKpiQueries.kpiHosts ? HostsKpiHostsRequestOptions : T extends HostsKpiQueries.kpiUniqueIps @@ -213,6 +215,8 @@ export type StrategyRequestType = T extends HostsQu ? TotalUsersKpiRequestOptions : T extends UsersQueries.users ? UsersRequestOptions + : T extends UsersQueries.kpiAuthentications + ? UsersKpiAuthenticationsRequestOptions : T extends NetworkQueries.details ? NetworkDetailsRequestOptions : T extends NetworkQueries.dns diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts index 4223b9b2fa7ab..050610b9049c1 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/index.ts @@ -18,6 +18,7 @@ export enum UsersQueries { kpiTotalUsers = 'usersKpiTotalUsers', users = 'allUsers', authentications = 'authentications', + kpiAuthentications = 'usersKpiAuthentications', } export type UserskKpiStrategyResponse = Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts similarity index 56% rename from x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts rename to x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts index 4a9a1383e228f..342bd601291a5 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/kpi/authentications/index.ts @@ -7,19 +7,18 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { Inspect, Maybe } from '../../../../common'; -import { RequestBasicOptions } from '../../..'; -import { HostsKpiHistogramData } from '../common'; +import { KpiHistogramData, RequestBasicOptions } from '../../..'; -export interface HostsKpiAuthenticationsHistogramCount { +export interface UsersKpiAuthenticationsHistogramCount { doc_count: number; } -export type HostsKpiAuthenticationsRequestOptions = RequestBasicOptions; +export type UsersKpiAuthenticationsRequestOptions = RequestBasicOptions; -export interface HostsKpiAuthenticationsStrategyResponse extends IEsSearchResponse { +export interface UsersKpiAuthenticationsStrategyResponse extends IEsSearchResponse { authenticationsSuccess: Maybe; - authenticationsSuccessHistogram: Maybe; + authenticationsSuccessHistogram: Maybe; authenticationsFailure: Maybe; - authenticationsFailureHistogram: Maybe; + authenticationsFailureHistogram: Maybe; inspect?: Maybe; } diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index f4ad0c3ddfc43..08decb153021e 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -36,7 +36,6 @@ export const userDetailsUrl = (userName: string) => `/app/security/users/${userN export const HOSTS_PAGE_TAB_URLS = { allHosts: '/app/security/hosts/allHosts', anomalies: '/app/security/hosts/anomalies', - authentications: '/app/security/hosts/authentications', events: '/app/security/hosts/events', uncommonProcesses: '/app/security/hosts/uncommonProcesses', }; diff --git a/x-pack/plugins/security_solution/cypress/urls/state.ts b/x-pack/plugins/security_solution/cypress/urls/state.ts index cc61ca6858a43..e491a6939bccd 100644 --- a/x-pack/plugins/security_solution/cypress/urls/state.ts +++ b/x-pack/plugins/security_solution/cypress/urls/state.ts @@ -17,7 +17,7 @@ export const ABSOLUTE_DATE_RANGE = { urlKqlHostsNetwork: `/app/security/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))`, urlKqlHostsHosts: `/app/security/hosts/allHosts?query=(language:kuery,query:'source.ip:%20"10.142.0.9"')&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))`, urlHost: - '/app/security/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))', + '/app/security/hosts/allHosts?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))', urlHostNew: - '/app/security/hosts/authentications?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272023-01-01T21:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272023-01-01T21:33:29.186Z%27)))', + '/app/security/hosts/allHosts?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272023-01-01T21:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272023-01-01T21:33:29.186Z%27)))', }; diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts index 85bc03d96b983..409b27c9ede41 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts @@ -140,32 +140,14 @@ describe('deepLinks', () => { ).toBeTruthy(); }); - describe('experimental flags', () => { - it('should return NO users link when enableExperimental.usersEnabled === false', () => { - const deepLinks = getDeepLinks({ - ...mockGlobalState.app.enableExperimental, - usersEnabled: false, - }); - - expect(findDeepLink(SecurityPageName.users, deepLinks)).toBeFalsy(); - }); - - it('should return users link when enableExperimental.usersEnabled === true', () => { - const deepLinks = getDeepLinks({ - ...mockGlobalState.app.enableExperimental, - usersEnabled: true, - }); - expect(findDeepLink(SecurityPageName.users, deepLinks)).toBeTruthy(); - }); - - it('should NOT return host authentications when enableExperimental.usersEnabled === true', () => { - const deepLinks = getDeepLinks({ - ...mockGlobalState.app.enableExperimental, - usersEnabled: true, - }); - expect(findDeepLink(SecurityPageName.hostsAuthentications, deepLinks)).toBeFalsy(); + it('should return users link', () => { + const deepLinks = getDeepLinks({ + ...mockGlobalState.app.enableExperimental, }); + expect(findDeepLink(SecurityPageName.users, deepLinks)).toBeTruthy(); + }); + describe('experimental flags', () => { it('should return NO kubernetes link when enableExperimental.kubernetesEnabled === false', () => { const deepLinks = getDeepLinks({ ...mockGlobalState.app.enableExperimental, diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 49c90c4479187..5774144999b27 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -225,14 +225,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ ], order: 9002, deepLinks: [ - { - id: SecurityPageName.hostsAuthentications, - title: i18n.translate('xpack.securitySolution.search.hosts.authentications', { - defaultMessage: 'Authentications', - }), - path: `${HOSTS_PATH}/authentications`, - hideWhenExperimentalKey: 'usersEnabled', - }, { id: SecurityPageName.uncommonProcesses, title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', { @@ -334,7 +326,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ title: USERS, path: USERS_PATH, navLinkStatus: AppNavLinkStatus.visible, - experimentalKey: 'usersEnabled', keywords: [ i18n.translate('xpack.securitySolution.search.users', { defaultMessage: 'Users', diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx index 0a78bb79c92fc..d462b8bb5f2e4 100644 --- a/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/authentication/authentications_host_table.tsx @@ -11,8 +11,6 @@ import { getOr } from 'lodash/fp'; import { useDispatch } from 'react-redux'; import { PaginatedTable } from '../paginated_table'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; - import * as i18n from './translations'; import { getHostDetailsAuthenticationColumns, @@ -41,7 +39,6 @@ const AuthenticationsHostTableComponent: React.FC = ( setQuery, deleteQuery, }) => { - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); const dispatch = useDispatch(); const { toggleStatus } = useQueryToggle(TABLE_QUERY_ID); const [querySkip, setQuerySkip] = useState(skip || !toggleStatus); @@ -70,8 +67,8 @@ const AuthenticationsHostTableComponent: React.FC = ( const columns = type === hostsModel.HostsType.details - ? getHostDetailsAuthenticationColumns(usersEnabled) - : getHostsPageAuthenticationColumns(usersEnabled); + ? getHostDetailsAuthenticationColumns() + : getHostsPageAuthenticationColumns(); const updateLimitPagination = useCallback( (newLimit) => diff --git a/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx index b4fc71930d641..38c4f55cb6e3b 100644 --- a/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/authentication/helpers.tsx @@ -25,8 +25,8 @@ import { import { LensAttributes } from '../visualization_actions/types'; import { authenticationLensAttributes } from '../visualization_actions/lens_attributes/common/authentication'; -export const getHostDetailsAuthenticationColumns = (usersEnabled: boolean): AuthTableColumns => [ - getUserColumn(usersEnabled), +export const getHostDetailsAuthenticationColumns = (): AuthTableColumns => [ + USER_COLUMN, SUCCESS_COLUMN, FAILURES_COLUMN, LAST_SUCCESSFUL_TIME_COLUMN, @@ -35,8 +35,8 @@ export const getHostDetailsAuthenticationColumns = (usersEnabled: boolean): Auth LAST_FAILED_SOURCE_COLUMN, ]; -export const getHostsPageAuthenticationColumns = (usersEnabled: boolean): AuthTableColumns => [ - getUserColumn(usersEnabled), +export const getHostsPageAuthenticationColumns = (): AuthTableColumns => [ + USER_COLUMN, SUCCESS_COLUMN, FAILURES_COLUMN, LAST_SUCCESSFUL_TIME_COLUMN, @@ -48,7 +48,7 @@ export const getHostsPageAuthenticationColumns = (usersEnabled: boolean): AuthTa ]; export const getUsersPageAuthenticationColumns = (): AuthTableColumns => - getHostsPageAuthenticationColumns(true); + getHostsPageAuthenticationColumns(); export const getUserDetailsAuthenticationColumns = (): AuthTableColumns => [ HOST_COLUMN, @@ -157,9 +157,7 @@ const LAST_FAILED_DESTINATION_COLUMN: Columns => ({ +const USER_COLUMN: Columns = { name: i18n.USER, truncateText: false, mobileOptions: { show: true }, @@ -170,9 +168,9 @@ const getUserColumn = ( isAggregatable: true, fieldType: 'keyword', idPrefix: `authentications-table-${node._id}-userName`, - render: (item) => (usersEnabled ? : <>{item}), + render: (item) => , }), -}); +}; const HOST_COLUMN: Columns = { name: i18n.HOST, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index b71da7cbdd6ff..594ceb1485adc 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -56,7 +56,7 @@ describe('Table Navigation', () => { const mockProps: TabNavigationProps & RouteSpyState = { pageName: 'hosts', pathName: '/hosts', - detailName: undefined, + detailName: hostName, search: '', tabName: HostsTableType.authentications, navTabs: navTabsHostDetails({ diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap index 9cbf51fe61441..0be5c860e22a2 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap @@ -105,6 +105,16 @@ Object { "name": "Network", "onClick": [Function], }, + Object { + "data-href": "securitySolutionUI/users?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-users", + "disabled": false, + "href": "securitySolutionUI/users?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "users", + "isSelected": false, + "name": "Users", + "onClick": [Function], + }, ], "name": "Explore", }, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx index 8f1dab106d905..e8ea703fe7171 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx @@ -33,7 +33,6 @@ export const useSecuritySolutionNavigation = () => { const { detailName, flowTarget, pageName, pathName, search, state, tabName } = routeProps; const disabledNavTabs = [ - ...(!useIsExperimentalFeatureEnabled('usersEnabled') ? ['users'] : []), ...(!useIsExperimentalFeatureEnabled('kubernetesEnabled') ? ['kubernetes'] : []), ]; const enabledNavTabs: GenericNavRecord = omit(disabledNavTabs, navTabs); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentication_metric_failure.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentication_metric_failure.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure.ts diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_area.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_bar.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_bar.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar.ts diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_metric_success.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_metric_success.ts rename to x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/field_value.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/field_value.tsx index b9a1e9b6cb126..c4801f1caf955 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/field_value.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/field_value.tsx @@ -18,7 +18,6 @@ import { FormattedFieldValue } from '../../../timelines/components/timeline/body import { parseValue } from '../../../timelines/components/timeline/body/renderers/parse_value'; import { EmptyComponent, getLinkColumnDefinition } from './helpers'; import { getField, getFieldKey } from '../../../helpers'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; const useFormattedFieldProps = ({ rowIndex, @@ -36,9 +35,8 @@ const useFormattedFieldProps = ({ pageSize: number; }) => { const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); const ecs = ecsData[pageRowIndex]; - const link = getLinkColumnDefinition(columnId, header?.type, header?.linkField, usersEnabled); + const link = getLinkColumnDefinition(columnId, header?.type, header?.linkField); const linkField = header?.linkField ? header?.linkField : link?.linkField; const linkValues = header && getOr([], linkField ?? '', ecs); const eventId = (header && get('_id' ?? '', ecs)) || ''; @@ -60,8 +58,7 @@ const useFormattedFieldProps = ({ const normalizedLink = getLinkColumnDefinition( normalizedColumnId, header?.type, - normalizedLinkField, - usersEnabled + normalizedLinkField ); return { pageRowIndex, diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/helpers.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/helpers.tsx index 507e34a9ccc01..4720a5c0a7b54 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/helpers.tsx @@ -65,26 +65,18 @@ export const COLUMNS_WITH_LINKS = [ columnId: INDICATOR_REFERENCE, label: i18n.VIEW_INDICATOR_REFERENCE, }, + { + columnId: USER_NAME_FIELD_NAME, + label: i18n.VIEW_USER_SUMMARY, + }, ]; export const getLinkColumnDefinition = ( columnIdToFind: string, fieldType?: string, - linkField?: string, - usersEnabled?: boolean -) => { - // TOOD move user name field to COLUMNS_WITH_LINKS when experiment is finished - return ( - usersEnabled - ? [ - ...COLUMNS_WITH_LINKS, - { - columnId: USER_NAME_FIELD_NAME, - label: i18n.VIEW_USER_SUMMARY, - }, - ] - : COLUMNS_WITH_LINKS - ).find((column) => { + linkField?: string +) => + COLUMNS_WITH_LINKS.find((column) => { if (column.columnId === columnIdToFind) { return true; } else if ( @@ -97,7 +89,6 @@ export const getLinkColumnDefinition = ( return false; } }); -}; /** a noop required by the filter in / out buttons */ export const onFilterAdded = () => {}; diff --git a/x-pack/plugins/security_solution/public/common/links/links.test.ts b/x-pack/plugins/security_solution/public/common/links/links.test.ts index 7493cab82a48e..8d9ebda596fba 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.test.ts +++ b/x-pack/plugins/security_solution/public/common/links/links.test.ts @@ -27,9 +27,9 @@ const defaultAppLinks: AppLinkItems = [ path: '/hosts', links: [ { - id: SecurityPageName.hostsAuthentications, - title: 'Authentications', - path: `/hosts/authentications`, + id: SecurityPageName.hostsAnomalies, + title: 'Anomalies', + path: `/hosts/anomalies`, }, { id: SecurityPageName.hostsEvents, @@ -48,6 +48,25 @@ const mockCapabilities = { [SERVER_APP_ID]: { show: true }, } as unknown as Capabilities; +const fakePageId = 'fakePage'; +const testFeatureflag = 'detectionResponseEnabled'; + +jest.mock('./app_links', () => { + const actual = jest.requireActual('./app_links'); + const fakeLink = { + id: fakePageId, + title: 'test fake menu item', + path: 'test fake path', + hideWhenExperimentalKey: testFeatureflag, + }; + + return { + ...actual, + getAppLinks: () => [...actual.appLinks, fakeLink], + appLinks: [...actual.appLinks, fakeLink], + }; +}); + const licenseBasicMock = jest.fn().mockImplementation((arg: LicenseType) => arg === 'basic'); const licensePremiumMock = jest.fn().mockReturnValue(true); const mockLicense = { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_table_cell_renderer.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_table_cell_renderer.tsx index 8d9edd2ebc92b..0497b6fa21a2b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_table_cell_renderer.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_table_cell_renderer.tsx @@ -8,7 +8,6 @@ import React, { useMemo } from 'react'; import { EuiDataGridCellValueElementProps } from '@elastic/eui'; import { CellValueElementProps } from '@kbn/timelines-plugin/common'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { StyledContent } from '../../../../common/lib/cell_actions/expanded_cell_value_actions'; import { getLinkColumnDefinition } from '../../../../common/lib/cell_actions/helpers'; import { useGetMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns'; @@ -70,14 +69,9 @@ export const PreviewTableCellRenderer: React.FC = ({ timelineId, truncate, }) => { - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); - const asPlainText = useMemo(() => { - return ( - getLinkColumnDefinition(header.id, header.type, undefined, usersEnabled) !== undefined && - !isTimeline - ); - }, [header.id, header.type, isTimeline, usersEnabled]); + return getLinkColumnDefinition(header.id, header.type, undefined) !== undefined && !isTimeline; + }, [header.id, header.type, isTimeline]); const values = useGetMappedNonEcsValue({ data, diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index 93a2e21dc84dc..4e7cfe622e43d 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; -import { HostsKpiAuthentications } from './authentications'; import { HostsKpiHosts } from './hosts'; import { HostsKpiUniqueIps } from './unique_ips'; import { HostsKpiProps } from './types'; @@ -16,12 +15,10 @@ import { CallOutSwitcher } from '../../../common/components/callouts'; import { RISKY_HOSTS_DOC_LINK } from '../../../overview/components/overview_risky_host_links/risky_hosts_disabled_module'; import * as i18n from './translations'; import { useHostRiskScore } from '../../../risk_score/containers'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; export const HostsKpiComponent = React.memo( ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => { const [_, { isModuleEnabled }] = useHostRiskScore({}); - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); return ( <> @@ -61,19 +58,6 @@ export const HostsKpiComponent = React.memo( skip={skip} /> - {!usersEnabled && ( - - - - )} ( ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => { - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); return ( - {!usersEnabled && ( - - - - )} { ); const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to }); @@ -223,7 +222,6 @@ const HostsComponent = () => { navTabs={navTabsHosts({ hasMlUserPermissions: hasMlUserPermissions(capabilities), isRiskyHostsEnabled: riskyHostsFeatureEnabled, - isUsersEnabled: usersEnabled, })} /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 15d40cd01d944..038300453101c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -22,7 +22,6 @@ import { HOSTS_PATH } from '../../../common/constants'; import { HostsQueryTabBody, HostRiskScoreQueryTabBody, - AuthenticationsQueryTabBody, UncommonProcessQueryTabBody, SessionsTabBody, } from './navigation'; @@ -87,9 +86,6 @@ export const HostsTabs = memo( - - - diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index 764281a48b737..8b45cf11aa604 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -19,7 +19,6 @@ import { hostDetailsPagePath } from './types'; const getHostsTabPath = () => `${HOSTS_PATH}/:tabName(` + `${HostsTableType.hosts}|` + - `${HostsTableType.authentications}|` + `${HostsTableType.uncommonProcesses}|` + `${HostsTableType.anomalies}|` + `${HostsTableType.events}|` + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx index b882dca3faaf1..c2228d3ced11b 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.test.tsx @@ -13,10 +13,8 @@ describe('navTabsHosts', () => { const tabs = navTabsHosts({ hasMlUserPermissions: false, isRiskyHostsEnabled: false, - isUsersEnabled: false, }); expect(tabs).toHaveProperty(HostsTableType.hosts); - expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).not.toHaveProperty(HostsTableType.anomalies); expect(tabs).toHaveProperty(HostsTableType.events); @@ -26,10 +24,8 @@ describe('navTabsHosts', () => { const tabs = navTabsHosts({ hasMlUserPermissions: true, isRiskyHostsEnabled: false, - isUsersEnabled: false, }); expect(tabs).toHaveProperty(HostsTableType.hosts); - expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).toHaveProperty(HostsTableType.anomalies); expect(tabs).toHaveProperty(HostsTableType.events); @@ -39,10 +35,8 @@ describe('navTabsHosts', () => { const tabs = navTabsHosts({ hasMlUserPermissions: false, isRiskyHostsEnabled: false, - isUsersEnabled: false, }); expect(tabs).toHaveProperty(HostsTableType.hosts); - expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).not.toHaveProperty(HostsTableType.risk); expect(tabs).toHaveProperty(HostsTableType.events); @@ -52,36 +46,10 @@ describe('navTabsHosts', () => { const tabs = navTabsHosts({ hasMlUserPermissions: false, isRiskyHostsEnabled: true, - isUsersEnabled: false, }); expect(tabs).toHaveProperty(HostsTableType.hosts); - expect(tabs).toHaveProperty(HostsTableType.authentications); expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); expect(tabs).toHaveProperty(HostsTableType.risk); expect(tabs).toHaveProperty(HostsTableType.events); }); - - test('it should skip authentications tab if isUsersEnabled is true', () => { - const tabs = navTabsHosts({ - hasMlUserPermissions: false, - isRiskyHostsEnabled: false, - isUsersEnabled: true, - }); - expect(tabs).toHaveProperty(HostsTableType.hosts); - expect(tabs).not.toHaveProperty(HostsTableType.authentications); - expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); - expect(tabs).toHaveProperty(HostsTableType.events); - }); - - test('it should display authentications tab if isUsersEnabled is false', () => { - const tabs = navTabsHosts({ - hasMlUserPermissions: false, - isRiskyHostsEnabled: false, - isUsersEnabled: false, - }); - expect(tabs).toHaveProperty(HostsTableType.hosts); - expect(tabs).toHaveProperty(HostsTableType.authentications); - expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses); - expect(tabs).toHaveProperty(HostsTableType.events); - }); }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx index ea46180f8df80..d0fda7fc4559b 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx @@ -16,11 +16,9 @@ const getTabsOnHostsUrl = (tabName: HostsTableType) => `${HOSTS_PATH}/${tabName} export const navTabsHosts = ({ hasMlUserPermissions, isRiskyHostsEnabled, - isUsersEnabled, }: { hasMlUserPermissions: boolean; isRiskyHostsEnabled: boolean; - isUsersEnabled: boolean; }): HostsNavTab => { const hiddenTabs = []; const hostsNavTabs = { @@ -30,12 +28,6 @@ export const navTabsHosts = ({ href: getTabsOnHostsUrl(HostsTableType.hosts), disabled: false, }, - [HostsTableType.authentications]: { - id: HostsTableType.authentications, - name: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, - href: getTabsOnHostsUrl(HostsTableType.authentications), - disabled: false, - }, [HostsTableType.uncommonProcesses]: { id: HostsTableType.uncommonProcesses, name: i18n.NAVIGATION_UNCOMMON_PROCESSES_TITLE, @@ -83,9 +75,5 @@ export const navTabsHosts = ({ hiddenTabs.push(HostsTableType.risk); } - if (isUsersEnabled) { - hiddenTabs.push(HostsTableType.authentications); - } - return omit(hiddenTabs, hostsNavTabs); }; diff --git a/x-pack/plugins/security_solution/public/hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/hosts/store/reducer.ts index 8b12cf39c35ac..dcbde903aaaa2 100644 --- a/x-pack/plugins/security_solution/public/hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/hosts/store/reducer.ts @@ -36,8 +36,8 @@ export const initialHostsState: HostsState = { }, [HostsTableType.hosts]: { activePage: DEFAULT_TABLE_ACTIVE_PAGE, - direction: Direction.desc, limit: DEFAULT_TABLE_LIMIT, + direction: Direction.desc, sortField: HostsFields.lastSeen, }, [HostsTableType.events]: { diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx index 13335a3e82e40..1df1ac9f9ac5f 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx @@ -35,7 +35,7 @@ type GetHostsRiskScoreProps = KpiRiskScoreRequestOptions & { signal: AbortSignal; }; -const getRiskyHosts = ({ +const getRiskScoreKpi = ({ data, defaultIndex, signal, @@ -55,19 +55,19 @@ const getRiskyHosts = ({ } ); -const getRiskyHostsComplete = ( +const getRiskScoreKpiComplete = ( props: GetHostsRiskScoreProps ): Observable => { - return getRiskyHosts(props).pipe( + return getRiskScoreKpi(props).pipe( filter((response) => { return isErrorResponse(response) || isCompleteResponse(response); }) ); }; -const getRiskyHostsWithOptionalSignal = withOptionalSignal(getRiskyHostsComplete); +const getRiskScoreKpiWithOptionalSignal = withOptionalSignal(getRiskScoreKpiComplete); -const useRiskyHostsComplete = () => useObservable(getRiskyHostsWithOptionalSignal); +const useRiskScoreKpiComplete = () => useObservable(getRiskScoreKpiWithOptionalSignal); interface RiskScoreKpi { error: unknown; @@ -91,14 +91,14 @@ export const useUserRiskScoreKpi = ({ }: UseUserRiskScoreKpiProps): RiskScoreKpi => { const spaceId = useSpaceId(); const defaultIndex = spaceId ? getUserRiskIndex(spaceId) : undefined; - const usersFeatureEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); + const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); return useRiskScoreKpi({ filterQuery, skip, defaultIndex, aggBy: 'user.name', - featureEnabled: usersFeatureEnabled, + featureEnabled: riskyUsersFeatureEnabled, }); }; @@ -134,7 +134,7 @@ const useRiskScoreKpi = ({ aggBy, featureEnabled, }: UseRiskScoreKpiProps): RiskScoreKpi => { - const { error, result, start, loading } = useRiskyHostsComplete(); + const { error, result, start, loading } = useRiskScoreKpiComplete(); const { data } = useKibana().services; const isModuleDisabled = !!error && isIndexNotFoundError(error); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index c95971f68c06e..704202be12228 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -18,7 +18,6 @@ import { Duration, EVENT_DURATION_FIELD_NAME } from '../../../duration'; import { getOrEmptyTagFromValue } from '../../../../../common/components/empty_value'; import { FormattedDate } from '../../../../../common/components/formatted_date'; import { FormattedIp } from '../../../formatted_ip'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { Port } from '../../../../../network/components/port'; import { PORT_NAMES } from '../../../../../network/components/port/helpers'; import { TruncatableText } from '../../../../../common/components/truncatable_text'; @@ -84,7 +83,6 @@ const FormattedFieldValueComponent: React.FC<{ value, linkValue, }) => { - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); if (isObjectArray || asPlainText) { return {value}; } else if (fieldType === IP_FIELD_TYPE) { @@ -165,7 +163,7 @@ const FormattedFieldValueComponent: React.FC<{ value={value} /> ); - } else if (usersEnabled && fieldName === USER_NAME_FIELD_NAME) { + } else if (fieldName === USER_NAME_FIELD_NAME) { return ( = ({ timelineId, truncate, }) => { - const usersEnabled = useIsExperimentalFeatureEnabled('usersEnabled'); - const asPlainText = useMemo(() => { - return ( - getLinkColumnDefinition(header.id, header.type, undefined, usersEnabled) !== undefined && - !isTimeline - ); - }, [header.id, header.type, isTimeline, usersEnabled]); + return getLinkColumnDefinition(header.id, header.type, undefined) !== undefined && !isTimeline; + }, [header.id, header.type, isTimeline]); const values = useGetMappedNonEcsValue({ data, diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.test.tsx b/x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/index.test.tsx similarity index 80% rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.test.tsx rename to x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/index.test.tsx index 08e517921e974..e3662dc164e34 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.test.tsx +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/index.test.tsx @@ -5,21 +5,21 @@ * 2.0. */ -import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications'; +import { useUsersKpiAuthentications } from '../../../containers/users/authentications'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import React from 'react'; -import { HostsKpiAuthentications } from '.'; +import { UsersKpiAuthentications } from '.'; jest.mock('../../../../common/containers/query_toggle'); -jest.mock('../../../containers/kpi_hosts/authentications'); -jest.mock('../common', () => ({ +jest.mock('../../../containers/users/authentications'); +jest.mock('../../../../hosts/components/kpi_hosts/common', () => ({ KpiBaseComponentManage: () => , })); describe('Authentications KPI', () => { - const mockUseHostsKpiAuthentications = useHostsKpiAuthentications as jest.Mock; + const mockUseHostsKpiAuthentications = useUsersKpiAuthentications as jest.Mock; const mockUseQueryToggle = useQueryToggle as jest.Mock; const defaultProps = { from: '2019-06-25T04:31:59.345Z', @@ -49,7 +49,7 @@ describe('Authentications KPI', () => { it('toggleStatus=true, do not skip', () => { render( - + ); expect(mockUseHostsKpiAuthentications.mock.calls[0][0].skip).toEqual(false); @@ -58,7 +58,7 @@ describe('Authentications KPI', () => { mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() }); render( - + ); expect(mockUseHostsKpiAuthentications.mock.calls[0][0].skip).toEqual(true); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/index.tsx similarity index 72% rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx rename to x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/index.tsx index f12eca88ffc95..562f08b8f9b37 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/index.tsx @@ -8,15 +8,20 @@ import React, { useEffect, useState } from 'react'; import { StatItems } from '../../../../common/components/stat_items'; -import { kpiUserAuthenticationsAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_area'; -import { kpiUserAuthenticationsBarLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_bar'; -import { kpiUserAuthenticationsMetricSuccessLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentications_metric_success'; -import { kpiUserAuthenticationsMetricFailureLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/hosts/kpi_user_authentication_metric_failure'; -import { useHostsKpiAuthentications, ID } from '../../../containers/kpi_hosts/authentications'; -import { KpiBaseComponentManage } from '../common'; -import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import { kpiUserAuthenticationsAreaLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area'; +import { kpiUserAuthenticationsBarLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_bar'; +import { kpiUserAuthenticationsMetricSuccessLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_metric_success'; +import { kpiUserAuthenticationsMetricFailureLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/users/kpi_user_authentication_metric_failure'; +import { useUsersKpiAuthentications, ID } from '../../../containers/users/authentications'; +import { KpiBaseComponentManage } from '../../../../hosts/components/kpi_hosts/common'; import * as i18n from './translations'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; +import { UsersKpiProps } from '../types'; + +enum ChartColors { + authenticationsSuccess = '#54B399', + authenticationsFailure = '#E7664C', +} export const fieldsMapping: Readonly = [ { @@ -27,7 +32,7 @@ export const fieldsMapping: Readonly = [ name: i18n.SUCCESS_CHART_LABEL, description: i18n.SUCCESS_UNIT_LABEL, value: null, - color: HostsKpiChartColors.authenticationsSuccess, + color: ChartColors.authenticationsSuccess, icon: 'check', lensAttributes: kpiUserAuthenticationsMetricSuccessLensAttributes, }, @@ -36,7 +41,7 @@ export const fieldsMapping: Readonly = [ name: i18n.FAIL_CHART_LABEL, description: i18n.FAIL_UNIT_LABEL, value: null, - color: HostsKpiChartColors.authenticationsFailure, + color: ChartColors.authenticationsFailure, icon: 'cross', lensAttributes: kpiUserAuthenticationsMetricFailureLensAttributes, }, @@ -49,7 +54,7 @@ export const fieldsMapping: Readonly = [ }, ]; -const HostsKpiAuthenticationsComponent: React.FC = ({ +const UsersKpiAuthenticationsComponent: React.FC = ({ filterQuery, from, indexNames, @@ -63,7 +68,7 @@ const HostsKpiAuthenticationsComponent: React.FC = ({ useEffect(() => { setQuerySkip(skip || !toggleStatus); }, [skip, toggleStatus]); - const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({ + const [loading, { refetch, id, inspect, ...data }] = useUsersKpiAuthentications({ filterQuery, endDate: to, indexNames, @@ -88,6 +93,6 @@ const HostsKpiAuthenticationsComponent: React.FC = ({ ); }; -HostsKpiAuthenticationsComponent.displayName = 'HostsKpiAuthenticationsComponent'; +UsersKpiAuthenticationsComponent.displayName = 'UsersKpiAuthenticationsComponent'; -export const HostsKpiAuthentications = React.memo(HostsKpiAuthenticationsComponent); +export const UsersKpiAuthentications = React.memo(UsersKpiAuthenticationsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts b/x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts rename to x-pack/plugins/security_solution/public/users/components/kpi_users/authentications/translations.ts diff --git a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx index 843ec0b7c871e..37f488cf0d1ce 100644 --- a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx @@ -10,7 +10,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; import { UsersKpiProps } from './types'; -import { HostsKpiAuthentications } from '../../../hosts/components/kpi_hosts/authentications'; +import { UsersKpiAuthentications } from './authentications'; import { TotalUsersKpi } from './total_users'; import { useUserRiskScore } from '../../../risk_score/containers'; import { CallOutSwitcher } from '../../../common/components/callouts'; @@ -61,7 +61,7 @@ export const UsersKpiComponent = React.memo( - = [ }, ]; -export interface UsersKpiProps { - filterQuery?: string; - from: string; - to: string; - indexNames: string[]; - narrowDateRange: UpdateDateRange; - setQuery: GlobalTimeArgs['setQuery']; - skip: boolean; -} - const QUERY_ID = 'TotalUsersKpiQuery'; const TotalUsersKpiComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.test.tsx b/x-pack/plugins/security_solution/public/users/containers/users/authentications/index.test.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.test.tsx rename to x-pack/plugins/security_solution/public/users/containers/users/authentications/index.test.tsx index 3fd27eecaa912..54f816dc02c70 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.test.tsx +++ b/x-pack/plugins/security_solution/public/users/containers/users/authentications/index.test.tsx @@ -7,9 +7,9 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { TestProviders } from '../../../../common/mock'; -import { useHostsKpiAuthentications } from '.'; +import { useUsersKpiAuthentications } from '.'; -describe('kpi hosts - authentications', () => { +describe('kpi users - authentications', () => { it('skip = true will cancel any running request', () => { const abortSpy = jest.spyOn(AbortController.prototype, 'abort'); const localProps = { @@ -18,7 +18,7 @@ describe('kpi hosts - authentications', () => { indexNames: ['cool'], skip: false, }; - const { rerender } = renderHook(() => useHostsKpiAuthentications(localProps), { + const { rerender } = renderHook(() => useUsersKpiAuthentications(localProps), { wrapper: TestProviders, }); localProps.skip = true; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/users/containers/users/authentications/index.tsx similarity index 77% rename from x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx rename to x-pack/plugins/security_solution/public/users/containers/users/authentications/index.tsx index bf67c00c03cd9..081fd7c2be5fb 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/users/containers/users/authentications/index.tsx @@ -15,9 +15,9 @@ import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; import { - HostsKpiQueries, - HostsKpiAuthenticationsRequestOptions, - HostsKpiAuthenticationsStrategyResponse, + UsersKpiAuthenticationsRequestOptions, + UsersKpiAuthenticationsStrategyResponse, + UsersQueries, } from '../../../../../common/search_strategy'; import { ESTermQuery } from '../../../../../common/typed_json'; @@ -25,17 +25,17 @@ import * as i18n from './translations'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; -export const ID = 'hostsKpiAuthenticationsQuery'; +export const ID = 'usersKpiAuthenticationsQuery'; -export interface HostsKpiAuthenticationsArgs - extends Omit { +export interface UsersKpiAuthenticationsArgs + extends Omit { id: string; inspect: InspectResponse; isInspected: boolean; refetch: inputsModel.Refetch; } -interface UseHostsKpiAuthentications { +interface UseUsersKpiAuthentications { filterQuery?: ESTermQuery | string; endDate: string; indexNames: string[]; @@ -43,23 +43,23 @@ interface UseHostsKpiAuthentications { startDate: string; } -export const useHostsKpiAuthentications = ({ +export const useUsersKpiAuthentications = ({ filterQuery, endDate, indexNames, skip = false, startDate, -}: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { +}: UseUsersKpiAuthentications): [boolean, UsersKpiAuthenticationsArgs] => { const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = - useState(null); + const [usersKpiAuthenticationsRequest, setUsersKpiAuthenticationsRequest] = + useState(null); - const [hostsKpiAuthenticationsResponse, setHostsKpiAuthenticationsResponse] = - useState({ + const [usersKpiAuthenticationsResponse, setUsersKpiAuthenticationsResponse] = + useState({ authenticationsSuccess: 0, authenticationsSuccessHistogram: [], authenticationsFailure: 0, @@ -74,8 +74,8 @@ export const useHostsKpiAuthentications = ({ }); const { addError, addWarning } = useAppToasts(); - const hostsKpiAuthenticationsSearch = useCallback( - (request: HostsKpiAuthenticationsRequestOptions | null) => { + const usersKpiAuthenticationsSearch = useCallback( + (request: UsersKpiAuthenticationsRequestOptions | null) => { if (request == null || skip) { return; } @@ -85,7 +85,7 @@ export const useHostsKpiAuthentications = ({ setLoading(true); searchSubscription$.current = data.search - .search( + .search( request, { strategy: 'securitySolutionSearchStrategy', @@ -96,7 +96,7 @@ export const useHostsKpiAuthentications = ({ next: (response) => { if (!response.isPartial && !response.isRunning) { setLoading(false); - setHostsKpiAuthenticationsResponse((prevResponse) => ({ + setUsersKpiAuthenticationsResponse((prevResponse) => ({ ...prevResponse, authenticationsSuccess: response.authenticationsSuccess, authenticationsSuccessHistogram: response.authenticationsSuccessHistogram, @@ -108,14 +108,14 @@ export const useHostsKpiAuthentications = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); + addWarning(i18n.ERROR_USERS_KPI_AUTHENTICATIONS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); addError(msg, { - title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS, + title: i18n.FAIL_USERS_KPI_AUTHENTICATIONS, }); searchSubscription$.current.unsubscribe(); }, @@ -130,11 +130,11 @@ export const useHostsKpiAuthentications = ({ ); useEffect(() => { - setHostsKpiAuthenticationsRequest((prevRequest) => { + setUsersKpiAuthenticationsRequest((prevRequest) => { const myRequest = { ...(prevRequest ?? {}), defaultIndex: indexNames, - factoryQueryType: HostsKpiQueries.kpiAuthentications, + factoryQueryType: UsersQueries.kpiAuthentications, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -150,12 +150,12 @@ export const useHostsKpiAuthentications = ({ }, [indexNames, endDate, filterQuery, startDate]); useEffect(() => { - hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest); + usersKpiAuthenticationsSearch(usersKpiAuthenticationsRequest); return () => { searchSubscription$.current.unsubscribe(); abortCtrl.current.abort(); }; - }, [hostsKpiAuthenticationsRequest, hostsKpiAuthenticationsSearch]); + }, [usersKpiAuthenticationsRequest, usersKpiAuthenticationsSearch]); useEffect(() => { if (skip) { @@ -165,5 +165,5 @@ export const useHostsKpiAuthentications = ({ } }, [skip]); - return [loading, hostsKpiAuthenticationsResponse]; + return [loading, usersKpiAuthenticationsResponse]; }; diff --git a/x-pack/plugins/security_solution/public/users/containers/users/authentications/translations.ts b/x-pack/plugins/security_solution/public/users/containers/users/authentications/translations.ts new file mode 100644 index 0000000000000..7c434563d384b --- /dev/null +++ b/x-pack/plugins/security_solution/public/users/containers/users/authentications/translations.ts @@ -0,0 +1,22 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const ERROR_USERS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.usersKpiAuthentications.errorSearchDescription', + { + defaultMessage: `An error has occurred on users kpi authentications search`, + } +); + +export const FAIL_USERS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.usersKpiAuthentications.failSearchDescription', + { + defaultMessage: `Failed to run search on users kpi authentications`, + } +); diff --git a/x-pack/plugins/security_solution/public/users/links.ts b/x-pack/plugins/security_solution/public/users/links.ts index a06eaa1b9f566..f6f6c5144d74b 100644 --- a/x-pack/plugins/security_solution/public/users/links.ts +++ b/x-pack/plugins/security_solution/public/users/links.ts @@ -21,7 +21,6 @@ export const links: LinkItem = { }), path: USERS_PATH, globalNavEnabled: true, - experimentalKey: 'usersEnabled', globalSearchKeywords: [ i18n.translate('xpack.securitySolution.appLinks.users', { defaultMessage: 'Users', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index 3cac14fb56f80..a44f3628903cf 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -13,7 +13,6 @@ import { hostOverview } from './overview'; import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; -import { hostsKpiAuthentications } from './kpi/authentications'; import { hostsKpiHosts } from './kpi/hosts'; import { hostsKpiUniqueIps } from './kpi/unique_ips'; @@ -22,7 +21,6 @@ jest.mock('./details'); jest.mock('./overview'); jest.mock('./last_first_seen'); jest.mock('./uncommon_processes'); -jest.mock('./kpi/authentications'); jest.mock('./kpi/hosts'); jest.mock('./kpi/unique_ips'); @@ -34,7 +32,6 @@ describe('hostsFactory', () => { [HostsQueries.overview]: hostOverview, [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, - [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index d7a37f871e252..2549bf0279606 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -17,7 +17,6 @@ import { hostDetails } from './details'; import { hostOverview } from './overview'; import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; -import { hostsKpiAuthentications } from './kpi/authentications'; import { hostsKpiHosts } from './kpi/hosts'; import { hostsKpiUniqueIps } from './kpi/unique_ips'; @@ -30,7 +29,6 @@ export const hostsFactory: Record< [HostsQueries.overview]: hostOverview, [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, - [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts deleted file mode 100644 index 34b4a682d42de..0000000000000 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts +++ /dev/null @@ -1,22 +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 { - HostsKpiHistogram, - HostsKpiAuthenticationsHistogramCount, - HostsKpiHistogramData, -} from '../../../../../../../common/search_strategy'; - -export const formatAuthenticationsHistogramData = ( - data: Array> -): HostsKpiHistogramData[] | null => - data && data.length > 0 - ? data.map(({ key, count }) => ({ - x: key, - y: count.doc_count, - })) - : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts deleted file mode 100644 index 4ac9098274eae..0000000000000 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts +++ /dev/null @@ -1,65 +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 { getOr } from 'lodash/fp'; - -import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import { - HostsKpiQueries, - HostsKpiAuthenticationsStrategyResponse, - HostsKpiAuthenticationsRequestOptions, -} from '../../../../../../../common/search_strategy/security_solution/hosts'; -import { inspectStringifyObject } from '../../../../../../utils/build_query'; -import { SecuritySolutionFactory } from '../../../types'; -import { buildHostsKpiAuthenticationsQuery } from './query.hosts_kpi_authentications.dsl'; -import { formatAuthenticationsHistogramData } from './helpers'; - -export const hostsKpiAuthentications: SecuritySolutionFactory = - { - buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => - buildHostsKpiAuthenticationsQuery(options), - parse: async ( - options: HostsKpiAuthenticationsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))], - }; - - const authenticationsSuccessHistogram = getOr( - null, - 'aggregations.authentication_success_histogram.buckets', - response.rawResponse - ); - const authenticationsFailureHistogram = getOr( - null, - 'aggregations.authentication_failure_histogram.buckets', - response.rawResponse - ); - - return { - ...response, - inspect, - authenticationsSuccess: getOr( - null, - 'aggregations.authentication_success.doc_count', - response.rawResponse - ), - authenticationsSuccessHistogram: formatAuthenticationsHistogramData( - authenticationsSuccessHistogram - ), - authenticationsFailure: getOr( - null, - 'aggregations.authentication_failure.doc_count', - response.rawResponse - ), - authenticationsFailureHistogram: formatAuthenticationsHistogramData( - authenticationsFailureHistogram - ), - }; - }, - }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts index 027e72947de3b..823d523ad92fe 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -export * from './authentications'; export * from './common'; export * from './hosts'; export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts index 0720244a73ff3..86023c1190770 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/index.ts @@ -12,6 +12,7 @@ import { SecuritySolutionFactory } from '../types'; import { allUsers } from './all'; import { authentications } from './authentications'; import { userDetails } from './details'; +import { usersKpiAuthentications } from './kpi/authentications'; import { totalUsersKpi } from './kpi/total_users'; export const usersFactory: Record> = { @@ -19,4 +20,5 @@ export const usersFactory: Record = { + buildDsl: (options: UsersKpiAuthenticationsRequestOptions) => + buildUsersKpiAuthenticationsQuery(options), + parse: async ( + options: UsersKpiAuthenticationsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildUsersKpiAuthenticationsQuery(options))], + }; + + const authenticationsSuccessHistogram = getOr( + null, + 'aggregations.authentication_success_histogram.buckets', + response.rawResponse + ); + const authenticationsFailureHistogram = getOr( + null, + 'aggregations.authentication_failure_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + authenticationsSuccess: getOr( + null, + 'aggregations.authentication_success.doc_count', + response.rawResponse + ), + authenticationsSuccessHistogram: formatGeneralHistogramData(authenticationsSuccessHistogram), + authenticationsFailure: getOr( + null, + 'aggregations.authentication_failure.doc_count', + response.rawResponse + ), + authenticationsFailureHistogram: formatGeneralHistogramData(authenticationsFailureHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts similarity index 91% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts index a440038baa236..a74d4cb7f13b6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/kpi/authentications/query.users_kpi_authentications.dsl.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { HostsKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { UsersKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../../utils/build_query'; -export const buildHostsKpiAuthenticationsQuery = ({ +export const buildUsersKpiAuthenticationsQuery = ({ filterQuery, timerange: { from, to }, defaultIndex, -}: HostsKpiAuthenticationsRequestOptions) => { +}: UsersKpiAuthenticationsRequestOptions) => { const filter = [ ...createQueryFilterClauses(filterQuery), { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 52e4d54383d2d..7436341ea8e2d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -25809,8 +25809,6 @@ "xpack.securitySolution.hosts.topRiskScoreContributors.rankColumnTitle": "Rang", "xpack.securitySolution.hosts.topRiskScoreContributors.ruleNameColumnTitle": "Nom de règle", "xpack.securitySolution.hosts.topRiskScoreContributors.title": "Principaux contributeurs de score de risque", - "xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription": "Une erreur s'est produite lors de la recherche d'authentifications du KPI des hôtes", - "xpack.securitySolution.hostsKpiAuthentications.failSearchDescription": "Impossible de lancer une recherche sur les authentifications du KPI des hôtes", "xpack.securitySolution.hostsKpiHosts.errorSearchDescription": "Une erreur s'est produite lors de la recherche du KPI des hôtes", "xpack.securitySolution.hostsKpiHosts.failSearchDescription": "Impossible de lancer une recherche sur le KPI des hôtes", "xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription": "Une erreur s'est produite lors de la recherche des IP uniques du KPI des hôtes", @@ -26382,7 +26380,6 @@ "xpack.securitySolution.search.getStarted": "Premiers pas", "xpack.securitySolution.search.hosts": "Hôtes", "xpack.securitySolution.search.hosts.anomalies": "Anomalies", - "xpack.securitySolution.search.hosts.authentications": "Authentifications", "xpack.securitySolution.search.hosts.events": "Événements", "xpack.securitySolution.search.hosts.externalAlerts": "Alertes externes", "xpack.securitySolution.search.hosts.sessions": "Sessions", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index dd389cf7f652e..d40657fbd01bd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -25958,8 +25958,6 @@ "xpack.securitySolution.hosts.topRiskScoreContributors.rankColumnTitle": "ランク", "xpack.securitySolution.hosts.topRiskScoreContributors.ruleNameColumnTitle": "ルール名", "xpack.securitySolution.hosts.topRiskScoreContributors.title": "上位のリスクスコアの要因", - "xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription": "ホストKPI認証検索でエラーが発生しました", - "xpack.securitySolution.hostsKpiAuthentications.failSearchDescription": "ホストKPI認証で検索を実行できませんでした", "xpack.securitySolution.hostsKpiHosts.errorSearchDescription": "ホストKPIホスト検索でエラーが発生しました", "xpack.securitySolution.hostsKpiHosts.failSearchDescription": "ホストKPIホストで検索を実行できませんでした", "xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription": "ホストKPI一意のIP検索でエラーが発生しました", @@ -26531,7 +26529,6 @@ "xpack.securitySolution.search.getStarted": "はじめて使う", "xpack.securitySolution.search.hosts": "ホスト", "xpack.securitySolution.search.hosts.anomalies": "異常", - "xpack.securitySolution.search.hosts.authentications": "認証", "xpack.securitySolution.search.hosts.events": "イベント", "xpack.securitySolution.search.hosts.externalAlerts": "外部アラート", "xpack.securitySolution.search.hosts.sessions": "セッション", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f0d2ad90cd60b..d691c54302eae 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -25991,8 +25991,6 @@ "xpack.securitySolution.hosts.topRiskScoreContributors.rankColumnTitle": "排名", "xpack.securitySolution.hosts.topRiskScoreContributors.ruleNameColumnTitle": "规则名称", "xpack.securitySolution.hosts.topRiskScoreContributors.title": "风险分数主要因素", - "xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription": "搜索主机 KPI 身份验证时发生错误", - "xpack.securitySolution.hostsKpiAuthentications.failSearchDescription": "无法对主机 KPI 身份验证执行搜索", "xpack.securitySolution.hostsKpiHosts.errorSearchDescription": "搜索主机 KPI 主机时发生错误", "xpack.securitySolution.hostsKpiHosts.failSearchDescription": "无法对主机 KPI 主机执行搜索", "xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription": "搜索主机 KPI 唯一 IP 时发生错误", @@ -26564,7 +26562,6 @@ "xpack.securitySolution.search.getStarted": "入门", "xpack.securitySolution.search.hosts": "主机", "xpack.securitySolution.search.hosts.anomalies": "异常", - "xpack.securitySolution.search.hosts.authentications": "身份验证", "xpack.securitySolution.search.hosts.events": "事件", "xpack.securitySolution.search.hosts.externalAlerts": "外部告警", "xpack.securitySolution.search.hosts.sessions": "会话", diff --git a/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts b/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts index 8d6f5520d884b..15ff856b29221 100644 --- a/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts +++ b/x-pack/test/api_integration/apis/security_solution/kpi_hosts.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import { - HostsKpiAuthenticationsStrategyResponse, HostsKpiHostsStrategyResponse, HostsKpiQueries, HostsKpiUniqueIpsStrategyResponse, @@ -38,10 +37,6 @@ export default function ({ getService }: FtrProviderContext) { y: 1, }, ], - authSuccess: 0, - authSuccessHistogram: null, - authFailure: 0, - authFailureHistogram: null, uniqueSourceIps: 1, uniqueSourceIpsHistogram: [ { @@ -78,28 +73,6 @@ export default function ({ getService }: FtrProviderContext) { expect(kpiHosts.hosts).to.eql(expectedResult.hosts); }); - it('Make sure that we get KpiAuthentications data', async () => { - const body = await bsearch.send({ - supertest, - options: { - factoryQueryType: HostsKpiQueries.kpiAuthentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['filebeat-*'], - docValueFields: [], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(body.authenticationsSuccess).to.eql(expectedResult.authSuccess); - expect(body.authenticationsSuccessHistogram).to.eql(expectedResult.authSuccessHistogram); - expect(body.authenticationsFailure).to.eql(expectedResult.authFailure); - expect(body.authenticationsFailureHistogram).to.eql(expectedResult.authFailureHistogram); - }); - it('Make sure that we get KpiUniqueIps data', async () => { const body = await bsearch.send({ supertest, @@ -164,10 +137,6 @@ export default function ({ getService }: FtrProviderContext) { y: 1, }, ], - authSuccess: 0, - authSuccessHistogram: null, - authFailure: 0, - authFailureHistogram: null, uniqueSourceIps: 3, uniqueSourceIpsHistogram: [ { x: 1543276800000, y: 1 }, @@ -208,28 +177,6 @@ export default function ({ getService }: FtrProviderContext) { expect(kpiHosts.hosts).to.eql(expectedResult.hosts); }); - it('Make sure that we get KpiAuthentications data', async () => { - const body = await bsearch.send({ - supertest, - options: { - factoryQueryType: HostsKpiQueries.kpiAuthentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-*'], - docValueFields: [], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(body.authenticationsSuccess).to.eql(expectedResult.authSuccess); - expect(body.authenticationsSuccessHistogram).to.eql(expectedResult.authSuccessHistogram); - expect(body.authenticationsFailure).to.eql(expectedResult.authFailure); - expect(body.authenticationsFailureHistogram).to.eql(expectedResult.authFailureHistogram); - }); - it('Make sure that we get KpiUniqueIps data', async () => { const body = await bsearch.send({ supertest, diff --git a/x-pack/test/api_integration/apis/security_solution/kpi_users.ts b/x-pack/test/api_integration/apis/security_solution/kpi_users.ts new file mode 100644 index 0000000000000..154321b1b2eef --- /dev/null +++ b/x-pack/test/api_integration/apis/security_solution/kpi_users.ts @@ -0,0 +1,101 @@ +/* + * 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 { + UsersKpiAuthenticationsStrategyResponse, + UsersQueries, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const bsearch = getService('bsearch'); + + describe('Kpi Users', () => { + describe('With filebeat', () => { + before( + async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default') + ); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + const expectedResult = { + authSuccess: 0, + authSuccessHistogram: null, + authFailure: 0, + authFailureHistogram: null, + }; + + it('Make sure that we get KpiAuthentications data', async () => { + const body = await bsearch.send({ + supertest, + options: { + factoryQueryType: UsersQueries.kpiAuthentications, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['filebeat-*'], + docValueFields: [], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(body.authenticationsSuccess).to.eql(expectedResult.authSuccess); + expect(body.authenticationsSuccessHistogram).to.eql(expectedResult.authSuccessHistogram); + expect(body.authenticationsFailure).to.eql(expectedResult.authFailure); + expect(body.authenticationsFailureHistogram).to.eql(expectedResult.authFailureHistogram); + }); + }); + + describe('With auditbeat', () => { + before( + async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/default') + ); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/default') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + const expectedResult = { + authSuccess: 0, + authSuccessHistogram: null, + authFailure: 0, + authFailureHistogram: null, + }; + + it('Make sure that we get KpiAuthentications data', async () => { + const body = await bsearch.send({ + supertest, + options: { + factoryQueryType: UsersQueries.kpiAuthentications, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['auditbeat-*'], + docValueFields: [], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(body.authenticationsSuccess).to.eql(expectedResult.authSuccess); + expect(body.authenticationsSuccessHistogram).to.eql(expectedResult.authSuccessHistogram); + expect(body.authenticationsFailure).to.eql(expectedResult.authFailure); + expect(body.authenticationsFailureHistogram).to.eql(expectedResult.authFailureHistogram); + }); + }); + }); +} From 660a69569a97055923b924433b5d200d950569e2 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 9 Jun 2022 11:12:45 +0200 Subject: [PATCH 24/44] [Cases] Update duration when pushing a case and closure options is set to automatically (#133602) * Update duration when pushing * Fix tests * PR feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/cases/common/api/metrics/case.ts | 2 +- .../components/all_cases/cases_metrics.tsx | 2 +- .../plugins/cases/server/client/cases/push.ts | 8 +++- .../cases/server/client/cases/utils.ts | 38 +++++++++++-------- .../aggregations/avg_duration.test.ts | 6 +-- .../all_cases/aggregations/avg_duration.ts | 2 +- .../client/metrics/all_cases/mttr.test.ts | 12 +++--- .../tests/common/metrics/get_cases_metrics.ts | 10 ++--- .../tests/trial/cases/push_case.ts | 26 +++++++++++++ 9 files changed, 72 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/cases/common/api/metrics/case.ts b/x-pack/plugins/cases/common/api/metrics/case.ts index 2c3a65764769f..0f15ea607572b 100644 --- a/x-pack/plugins/cases/common/api/metrics/case.ts +++ b/x-pack/plugins/cases/common/api/metrics/case.ts @@ -187,6 +187,6 @@ export const CasesMetricsResponseRt = rt.partial( /** * The average resolve time of all cases in seconds */ - mttr: rt.number, + mttr: rt.union([rt.number, rt.null]), }).props ); diff --git a/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx b/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx index 3325b6de4ebcc..e75f0fd8a6708 100644 --- a/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx @@ -52,7 +52,7 @@ export const CasesMetrics: FunctionComponent = ({ refresh }) => { const { mttr, isLoading: isCasesMetricsLoading, fetchCasesMetrics } = useGetCasesMetrics(); const mttrValue = useMemo( - () => (mttr ? prettyMilliseconds(mttr * 1000, { compact: true, verbose: false }) : '-'), + () => (mttr != null ? prettyMilliseconds(mttr * 1000, { compact: true, verbose: false }) : '-'), [mttr] ); diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 42fe71f902a72..cfdec1da5e775 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -23,7 +23,7 @@ import { } from '../../../common/api'; import { CASE_COMMENT_SAVED_OBJECT } from '../../../common/constants'; -import { createIncident, getCommentContextFromAttributes } from './utils'; +import { createIncident, getCommentContextFromAttributes, getDurationInSeconds } from './utils'; import { createCaseError } from '../../common/error'; import { createAlertUpdateRequest, @@ -226,6 +226,12 @@ export const push = async ( closed_by: { email, full_name, username }, } : {}), + ...(shouldMarkAsClosed + ? getDurationInSeconds({ + closedAt: pushedDate, + createdAt: theCase.created_at, + }) + : {}), external_service: externalService, updated_at: pushedDate, updated_by: { username, full_name, email }, diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index 01c1a9ab897b5..e4230ad9e83ce 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -433,6 +433,27 @@ export const getClosedInfoForUpdate = ({ } }; +export const getDurationInSeconds = ({ + closedAt, + createdAt, +}: { + closedAt: string; + createdAt: CaseAttributes['created_at']; +}) => { + try { + if (createdAt != null && closedAt != null) { + const createdAtMillis = new Date(createdAt).getTime(); + const closedAtMillis = new Date(closedAt).getTime(); + + if (!isNaN(createdAtMillis) && !isNaN(closedAtMillis) && closedAtMillis >= createdAtMillis) { + return { duration: Math.floor((closedAtMillis - createdAtMillis) / 1000) }; + } + } + } catch (err) { + // Silence date errors + } +}; + export const getDurationForUpdate = ({ status, closedAt, @@ -443,22 +464,7 @@ export const getDurationForUpdate = ({ status?: CaseStatuses; }): Pick | undefined => { if (status && status === CaseStatuses.closed) { - try { - if (createdAt != null && closedAt != null) { - const createdAtMillis = new Date(createdAt).getTime(); - const closedAtMillis = new Date(closedAt).getTime(); - - if ( - !isNaN(createdAtMillis) && - !isNaN(closedAtMillis) && - closedAtMillis >= createdAtMillis - ) { - return { duration: Math.floor((closedAtMillis - createdAtMillis) / 1000) }; - } - } - } catch (err) { - // Silence date errors - } + return getDurationInSeconds({ createdAt, closedAt }); } if (status && (status === CaseStatuses.open || status === CaseStatuses['in-progress'])) { diff --git a/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.test.ts b/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.test.ts index 1e63332fd419b..0814d00dd0e17 100644 --- a/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.test.ts +++ b/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.test.ts @@ -30,19 +30,19 @@ describe('AverageDuration', () => { const agg = new AverageDuration(); // @ts-expect-error const res = agg.formatResponse(); - expect(res).toEqual({ mttr: 0 }); + expect(res).toEqual({ mttr: null }); }); it('formats the response correctly if the mttr is not defined', async () => { const agg = new AverageDuration(); const res = agg.formatResponse({}); - expect(res).toEqual({ mttr: 0 }); + expect(res).toEqual({ mttr: null }); }); it('formats the response correctly if the value is not defined', async () => { const agg = new AverageDuration(); const res = agg.formatResponse({ mttr: {} }); - expect(res).toEqual({ mttr: 0 }); + expect(res).toEqual({ mttr: null }); }); it('gets the name correctly', async () => { diff --git a/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.ts b/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.ts index afa0638a2cf0a..03d0595f7ec7a 100644 --- a/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.ts +++ b/x-pack/plugins/cases/server/client/metrics/all_cases/aggregations/avg_duration.ts @@ -23,7 +23,7 @@ export class AverageDuration implements AggregationBuilder formatResponse(aggregations: AggregationResponse) { const aggs = aggregations as MTTRAggregate; - const mttr = aggs?.mttr?.value ?? 0; + const mttr = aggs?.mttr?.value ?? null; return { mttr }; } diff --git a/x-pack/plugins/cases/server/client/metrics/all_cases/mttr.test.ts b/x-pack/plugins/cases/server/client/metrics/all_cases/mttr.test.ts index e133082e69756..e502809926213 100644 --- a/x-pack/plugins/cases/server/client/metrics/all_cases/mttr.test.ts +++ b/x-pack/plugins/cases/server/client/metrics/all_cases/mttr.test.ts @@ -43,28 +43,28 @@ describe('MTTR', () => { expect(await handler.compute()).toEqual({}); }); - it('returns zero values when aggregation returns undefined', async () => { + it('returns null when aggregation returns undefined', async () => { caseService.executeAggregations.mockResolvedValue(undefined); const handler = new MTTR(constructorOptions); handler.setupFeature('mttr'); - expect(await handler.compute()).toEqual({ mttr: 0 }); + expect(await handler.compute()).toEqual({ mttr: null }); }); - it('returns zero values when aggregation returns empty object', async () => { + it('returns null when aggregation returns empty object', async () => { caseService.executeAggregations.mockResolvedValue({}); const handler = new MTTR(constructorOptions); handler.setupFeature('mttr'); - expect(await handler.compute()).toEqual({ mttr: 0 }); + expect(await handler.compute()).toEqual({ mttr: null }); }); - it('returns zero values when aggregation returns empty mttr object', async () => { + it('returns null when aggregation returns empty mttr object', async () => { caseService.executeAggregations.mockResolvedValue({ mttr: {} }); const handler = new MTTR(constructorOptions); handler.setupFeature('mttr'); - expect(await handler.compute()).toEqual({ mttr: 0 }); + expect(await handler.compute()).toEqual({ mttr: null }); }); it('returns values when there is a mttr value', async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/metrics/get_cases_metrics.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/metrics/get_cases_metrics.ts index e3e0c2cb20570..4a053de2515ba 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/metrics/get_cases_metrics.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/metrics/get_cases_metrics.ts @@ -40,22 +40,22 @@ export default ({ getService }: FtrProviderContext): void => { features: 'mttr', }); - expect(metrics).to.eql({ mttr: 0 }); + expect(metrics).to.eql({ mttr: null }); await deleteAllCaseItems(es); }); describe('MTTR', () => { - it('responses with zero if there are no cases', async () => { + it('responses with null if there are no cases', async () => { const metrics = await getCasesMetrics({ supertest, features: ['mttr'], }); - expect(metrics).to.eql({ mttr: 0 }); + expect(metrics).to.eql({ mttr: null }); }); - it('responses with zero if there are only open case and in-progress cases', async () => { + it('responses with null if there are only open case and in-progress cases', async () => { await createCase(supertest, getPostCaseRequest()); const theCase = await createCase(supertest, getPostCaseRequest()); @@ -77,7 +77,7 @@ export default ({ getService }: FtrProviderContext): void => { features: ['mttr'], }); - expect(metrics).to.eql({ mttr: 0 }); + expect(metrics).to.eql({ mttr: null }); }); describe('closed and open cases from kbn archive', () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts index a92e4ee28ff5b..ea0751b2d3e98 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts @@ -36,6 +36,8 @@ import { getServiceNowSimulationServer, createConfiguration, getSignalsWithES, + delay, + calculateDuration, } from '../../../../common/lib/utils'; import { globalRead, @@ -297,6 +299,30 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + describe('duration', () => { + it('updates the duration correctly when pushed a case and case closure options is set to automatically', async () => { + const { postedCase, connector } = await createCaseWithConnector({ + configureReq: { + closure_type: 'close-by-pushing', + }, + supertest, + serviceNowSimulatorURL, + actionsRemover, + }); + + await delay(1000); + + const pushedCase = await pushCase({ + supertest, + caseId: postedCase.id, + connectorId: connector.id, + }); + + const duration = calculateDuration(pushedCase.closed_at, postedCase.created_at); + expect(duration).to.be(pushedCase.duration); + }); + }); + describe('alerts', () => { const defaultSignalsIndex = '.siem-signals-default-000001'; const signalID = '4679431ee0ba3209b6fcd60a255a696886fe0a7d18f5375de510ff5b68fa6b78'; From 4eacf5c2646614ee9216aec86c4890750768c934 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jun 2022 02:25:38 -0700 Subject: [PATCH 25/44] Update dependency elastic-apm-node to ^3.35.0 (#133415) Co-authored-by: Renovate Bot Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 67b1bea7b75de..c46800c67cf54 100644 --- a/package.json +++ b/package.json @@ -277,7 +277,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", - "elastic-apm-node": "^3.34.0", + "elastic-apm-node": "^3.35.0", "email-addresses": "^5.0.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index 4cb1bf8a2e7b3..c140a0148c6b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13248,10 +13248,10 @@ elastic-apm-http-client@11.0.1: semver "^6.3.0" stream-chopper "^3.0.1" -elastic-apm-node@^3.34.0: - version "3.34.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.34.0.tgz#dfe5a08735b0cc46980dc75238444dd4a2390aea" - integrity sha512-LzVSXDmA3HzyOr3RvpRaOB34I0gXCfpaEEZ/OLFvVodS/1MCDM5ywEulWko6cveSdn/gklcT0vCHv1cHg8vLSg== +elastic-apm-node@^3.35.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.35.0.tgz#dd388075e8945b867088b7a8ad0e364e838ff523" + integrity sha512-zug5tN13Cx6IdKNScil6FIgFxSDbRfss5MKoz9riWpTU0V0kLJjPFndNmqmUSCDnam+GehZat52IqITMd/3NXg== dependencies: "@elastic/ecs-pino-format" "^1.2.0" "@opentelemetry/api" "^1.1.0" From 129e044562164894f3484bc41785443d4d689485 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Thu, 9 Jun 2022 12:34:10 +0300 Subject: [PATCH 26/44] [Visualize] switching dashboard mode doesn't update missing data view prompt (#133873) Closes: #133785 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../visualizations/public/embeddable/visualize_embeddable.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index bf1af0f890adf..2ccf2703e5af3 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -393,7 +393,9 @@ export class VisualizeEmbeddable this.subscriptions.push(this.handler.render$.subscribe(this.onContainerRender)); this.subscriptions.push( - this.getOutput$().subscribe(({ error }) => { + this.getUpdated$().subscribe(() => { + const { error } = this.getOutput(); + if (error) { if (isFallbackDataView(this.vis.data.indexPattern)) { render( From efda0f322a0a33e816e03f57b3da7ad07c150c6f Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 9 Jun 2022 11:41:04 +0200 Subject: [PATCH 27/44] [Lens] Set horizontal legend size to auto (#133851) --- .../__snapshots__/to_expression.test.ts.snap | 2 +- .../xy_visualization/to_expression.test.ts | 37 +++++++++++++++++-- .../public/xy_visualization/to_expression.ts | 15 +++++--- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap index 46d7f535f090a..b36e6983ba309 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap @@ -167,7 +167,7 @@ Object { "legendSize": Array [], "maxLines": Array [], "position": Array [ - "bottom", + "left", ], "shouldTruncate": Array [ true, diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index 5c994961e6013..93afe9d5a6ae0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -72,7 +72,7 @@ describe('#toExpression', () => { expect( xyVisualization.toExpression( { - legend: { position: Position.Bottom, isVisible: true }, + legend: { position: Position.Left, isVisible: true }, valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'Carry', @@ -371,7 +371,7 @@ describe('#toExpression', () => { it('should set legend size for outside legend', () => { const expression = xyVisualization.toExpression( { - legend: { position: Position.Bottom, isVisible: true, legendSize: LegendSize.SMALL }, + legend: { position: Position.Left, isVisible: true, legendSize: LegendSize.SMALL }, valueLabels: 'show', preferredSeriesType: 'bar', layers: [ @@ -394,12 +394,43 @@ describe('#toExpression', () => { ).toEqual('small'); }); - it('should ignore legend size for insidee legend', () => { + it('should use auto legend size for bottom/top legend', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true, + isInside: false, + legendSize: LegendSize.SMALL, + }, + valueLabels: 'show', + preferredSeriesType: 'bar', + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + splitAccessor: 'd', + xAccessor: 'a', + accessors: ['b', 'c'], + }, + ], + }, + frame.datasourceLayers, + undefined, + datasourceExpressionsByLayers + ) as Ast; + expect((expression.chain[0].arguments.legend[0] as Ast).chain[0].arguments.legendSize[0]).toBe( + LegendSize.AUTO + ); + }); + + it('should ignore legend size for inside legend', () => { + const expression = xyVisualization.toExpression( + { + legend: { + position: Position.Left, + isVisible: true, isInside: true, legendSize: LegendSize.SMALL, }, diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 1cbd1aa866e18..1c6a1d37de86f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -6,11 +6,12 @@ */ import { Ast, AstFunction } from '@kbn/interpreter'; -import { ScaleType } from '@elastic/charts'; +import { Position, ScaleType } from '@elastic/charts'; import type { PaletteRegistry } from '@kbn/coloring'; import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; import type { AxisExtentConfig, YConfig, ExtendedYConfig } from '@kbn/expression-xy-plugin/common'; +import { LegendSize } from '@kbn/visualizations-plugin/public'; import { State, XYDataLayerConfig, @@ -213,10 +214,14 @@ export const buildExpression = ( : [], position: !state.legend.isInside ? [state.legend.position] : [], isInside: state.legend.isInside ? [state.legend.isInside] : [], - legendSize: - !state.legend.isInside && state.legend.legendSize - ? [state.legend.legendSize] - : [], + legendSize: state.legend.isInside + ? [] + : state.legend.position === Position.Top || + state.legend.position === Position.Bottom + ? [LegendSize.AUTO] + : state.legend.legendSize + ? [state.legend.legendSize] + : [], horizontalAlignment: state.legend.horizontalAlignment && state.legend.isInside ? [state.legend.horizontalAlignment] From 035940be89960b0969a435deed9c4ad6247026ed Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 9 Jun 2022 10:59:08 +0100 Subject: [PATCH 28/44] chore(NA): upgrades bazel to v5.2.0 (#133980) --- .bazelversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index ac14c3dfaa865..91ff57278e37e 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.1.1 +5.2.0 From 1c651ed9bfdf1f3647237332f483db5596a5461e Mon Sep 17 00:00:00 2001 From: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Date: Thu, 9 Jun 2022 14:29:30 +0200 Subject: [PATCH 29/44] [Lens] fix breaking color picker when value is incorrect (#133796) * [Lens] fix breaking color picker when value is incorrect * positive test * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * fix when removing * refactor * deeper refactoring * remove unused Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../xy_visualization/annotations/helpers.tsx | 22 +-- .../xy_visualization/color_assignment.ts | 110 ++++++++++----- .../reference_line_helpers.tsx | 2 +- .../public/xy_visualization/visualization.tsx | 4 +- .../annotations_panel.tsx | 1 + .../xy_config_panel/color_picker.tsx | 133 ++++++++---------- .../xy_config_panel/dimension_editor.tsx | 58 ++++++-- .../reference_line_panel.tsx | 1 + .../xy_config_panel/xy_config_panel.test.tsx | 72 ++++++++++ 9 files changed, 272 insertions(+), 131 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index f9f4b9da5342a..35a40623b72aa 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -12,6 +12,7 @@ import { defaultAnnotationRangeColor, isRangeAnnotation, } from '@kbn/event-annotation-plugin/public'; +import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; import { layerTypes } from '../../../common'; import type { FramePublicAPI, Visualization } from '../../types'; import { isHorizontalChart } from '../state_helpers'; @@ -168,17 +169,16 @@ export const setAnnotationsDimension: Visualization['setDimension'] = ( }; }; -export const getAnnotationsAccessorColorConfig = (layer: XYAnnotationLayerConfig) => { - return layer.annotations.map((annotation) => { - return { - columnId: annotation.id, - triggerIcon: annotation.isHidden ? ('invisible' as const) : ('color' as const), - color: - annotation?.color || - (isRangeAnnotation(annotation) ? defaultAnnotationRangeColor : defaultAnnotationColor), - }; - }); -}; +export const getSingleColorAnnotationConfig = (annotation: EventAnnotationConfig) => ({ + columnId: annotation.id, + triggerIcon: annotation.isHidden ? ('invisible' as const) : ('color' as const), + color: + annotation?.color || + (isRangeAnnotation(annotation) ? defaultAnnotationRangeColor : defaultAnnotationColor), +}); + +export const getAnnotationsAccessorColorConfig = (layer: XYAnnotationLayerConfig) => + layer.annotations.map((annotation) => getSingleColorAnnotationConfig(annotation)); export const getAnnotationsConfiguration = ({ state, diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 3ac89935455c7..f20c207955ad1 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -9,12 +9,20 @@ import { uniq, mapValues } from 'lodash'; import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring'; import type { Datatable } from '@kbn/expressions-plugin'; import { euiLightVars } from '@kbn/ui-theme'; +import { + defaultAnnotationColor, + defaultAnnotationRangeColor, + isRangeAnnotation, +} from '@kbn/event-annotation-plugin/public'; import type { AccessorConfig, FramePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; import { FormatFactory } from '../../common'; import { isDataLayer, isReferenceLayer, isAnnotationsLayer } from './visualization_helpers'; import { getAnnotationsAccessorColorConfig } from './annotations/helpers'; -import { getReferenceLineAccessorColorConfig } from './reference_line_helpers'; +import { + getReferenceLineAccessorColorConfig, + getSingleColorConfig, +} from './reference_line_helpers'; import { XYDataLayerConfig, XYLayerConfig } from './types'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -96,7 +104,67 @@ export function getColorAssignments( }); } -export function getAccessorColorConfig( +function getDisabledConfig(accessor: string) { + return { + columnId: accessor as string, + triggerIcon: 'disabled' as const, + }; +} + +export function getAssignedColorConfig( + layer: XYLayerConfig, + accessor: string, + colorAssignments: ColorAssignments, + frame: Pick, + paletteService: PaletteRegistry +): AccessorConfig { + if (isReferenceLayer(layer)) { + return getSingleColorConfig(accessor); + } + if (isAnnotationsLayer(layer)) { + const annotation = layer.annotations.find((a) => a.id === accessor); + return { + columnId: accessor, + triggerIcon: annotation?.isHidden ? ('invisible' as const) : ('color' as const), + color: isRangeAnnotation(annotation) ? defaultAnnotationRangeColor : defaultAnnotationColor, + }; + } + const layerContainsSplits = isDataLayer(layer) && !layer.collapseFn && layer.splitAccessor; + const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; + const totalSeriesCount = colorAssignments[currentPalette.name]?.totalSeriesCount; + + if (layerContainsSplits) { + return getDisabledConfig(accessor); + } + + const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); + const rank = colorAssignments[currentPalette.name].getRank( + layer, + columnToLabel[accessor] || accessor, + accessor + ); + const assignedColor = + totalSeriesCount != null + ? paletteService.get(currentPalette.name).getCategoricalColor( + [ + { + name: columnToLabel[accessor] || accessor, + rankAtDepth: rank, + totalSeriesAtDepth: totalSeriesCount, + }, + ], + { maxDepth: 1, totalSeries: totalSeriesCount }, + currentPalette.params + ) + : undefined; + return { + columnId: accessor as string, + triggerIcon: assignedColor ? 'color' : 'disabled', + color: assignedColor ?? undefined, + }; +} + +export function getAccessorColorConfigs( colorAssignments: ColorAssignments, frame: Pick, layer: XYLayerConfig, @@ -109,42 +177,18 @@ export function getAccessorColorConfig( return getAnnotationsAccessorColorConfig(layer); } const layerContainsSplits = !layer.collapseFn && layer.splitAccessor; - const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; - const totalSeriesCount = colorAssignments[currentPalette.name]?.totalSeriesCount; return layer.accessors.map((accessor) => { - const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); if (layerContainsSplits) { + return getDisabledConfig(accessor); + } + const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + if (currentYConfig?.color) { return { columnId: accessor as string, - triggerIcon: 'disabled', + triggerIcon: 'color', + color: currentYConfig.color, }; } - - const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); - const rank = colorAssignments[currentPalette.name].getRank( - layer, - columnToLabel[accessor] || accessor, - accessor - ); - const customColor = - currentYConfig?.color || - (totalSeriesCount != null - ? paletteService.get(currentPalette.name).getCategoricalColor( - [ - { - name: columnToLabel[accessor] || accessor, - rankAtDepth: rank, - totalSeriesAtDepth: totalSeriesCount, - }, - ], - { maxDepth: 1, totalSeries: totalSeriesCount }, - currentPalette.params - ) - : undefined); - return { - columnId: accessor as string, - triggerIcon: customColor ? 'color' : 'disabled', - color: customColor ?? undefined, - }; + return getAssignedColorConfig(layer, accessor, colorAssignments, frame, paletteService); }); } diff --git a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx index 5ddb1fcc043e7..3f0e8816cf6b1 100644 --- a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx @@ -370,7 +370,7 @@ export const setReferenceDimension: Visualization['setDimension'] = ({ }; }; -const getSingleColorConfig = (id: string, color = defaultReferenceLineColor) => ({ +export const getSingleColorConfig = (id: string, color = defaultReferenceLineColor) => ({ columnId: id, triggerIcon: 'color' as const, color, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index b35247f4d9d97..908a97d5c1c2d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -31,7 +31,7 @@ import { State, visualizationTypes, XYSuggestion, XYLayerConfig, XYDataLayerConf import { layerTypes } from '../../common'; import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; -import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; +import { getAccessorColorConfigs, getColorAssignments } from './color_assignment'; import { getColumnToLabelMap } from './state_helpers'; import { getGroupsAvailableInData, @@ -699,7 +699,7 @@ const getMappedAccessors = ({ { tables: frame.activeData }, fieldFormats.deserialize ); - mappedAccessors = getAccessorColorConfig( + mappedAccessors = getAccessorColorConfigs( colorAssignments, frame, { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/annotations_config_panel/annotations_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/annotations_config_panel/annotations_panel.tsx index 02a1858e5444b..7ce8eba19f1a9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/annotations_config_panel/annotations_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/annotations_config_panel/annotations_panel.tsx @@ -346,6 +346,7 @@ export const AnnotationsPanel = ( + (color && isValidColor(color) && chroma(color)?.alpha()) || 1; + export const ColorPicker = ({ - state, - layerId, - accessor, - frame, - formatFactory, - paletteService, label, disableHelpTooltip, disabled, setConfig, - showAlpha, defaultColor, -}: VisualizationDimensionEditorProps & { - formatFactory: FormatFactory; - paletteService: PaletteRegistry; + overwriteColor, + showAlpha, +}: { + overwriteColor?: string | null; + defaultColor?: string | null; + setConfig: (config: { color?: string }) => void; label?: string; disableHelpTooltip?: boolean; disabled?: boolean; - setConfig: (config: { color?: string }) => void; showAlpha?: boolean; - defaultColor?: string; }) => { - const index = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[index]; - - const overwriteColor = getSeriesColor(layer, accessor); - const currentColor = useMemo(() => { - if (overwriteColor || !frame.activeData) return overwriteColor; - if (defaultColor) { - return defaultColor; - } - if (isDataLayer(layer)) { - const sortedAccessors: string[] = getSortedAccessors( - frame.datasourceLayers[layer.layerId] ?? layer.accessors, - layer - ); - const colorAssignments = getColorAssignments( - getDataLayers(state.layers), - { tables: frame.activeData ?? {} }, - formatFactory - ); - const mappedAccessors = getAccessorColorConfig( - colorAssignments, - frame, - { - ...layer, - accessors: sortedAccessors.filter((sorted) => layer.accessors.includes(sorted)), - }, - paletteService - ); - return mappedAccessors.find((a) => a.columnId === accessor)?.color || null; - } - }, [ - overwriteColor, - frame, - paletteService, - state.layers, - accessor, - formatFactory, - layer, - defaultColor, - ]); - - const [color, setColor] = useState(currentColor); + const [color, setColor] = useState(overwriteColor || defaultColor); + const [hexColor, setHexColor] = useState(overwriteColor || defaultColor); + const [currentColorAlpha, setCurrentColorAlpha] = useState(getColorAlpha(color)); + const unflushedChanges = useRef(false); useEffect(() => { - setColor(currentColor); - }, [currentColor]); + // only the changes from outside the color picker should be applied + if (!unflushedChanges.current) { + // something external changed the color that is currently selected (switching from annotation line to annotation range) + if (overwriteColor && hexColor && overwriteColor !== hexColor) { + setColor(overwriteColor || defaultColor); + setCurrentColorAlpha(getColorAlpha(overwriteColor)); + } + } + unflushedChanges.current = false; + }, [hexColor, overwriteColor, defaultColor]); const handleColor: EuiColorPickerProps['onChange'] = (text, output) => { setColor(text); - if (output.isValid || text === '') { - const newColor = text === '' ? undefined : output.hex; - setConfig({ color: newColor }); + unflushedChanges.current = true; + if (output.isValid) { + setHexColor(output.hex); + setCurrentColorAlpha(chroma(output.hex)?.alpha() || 1); + setConfig({ color: output.hex }); + } + if (text === '') { + setConfig({ color: undefined }); } }; @@ -123,8 +106,6 @@ export const ColorPicker = ({ defaultMessage: 'Series color', }); - const currentColorAlpha = color ? chroma(color).alpha() : 1; - const colorPicker = ( - {''} = T extends Array ? P : T; @@ -44,6 +46,25 @@ export function DimensionEditor( formatFactory: FormatFactory; paletteService: PaletteRegistry; } +) { + const { state, layerId } = props; + const index = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[index]; + if (isAnnotationsLayer(layer)) { + return ; + } + + if (isReferenceLayer(layer)) { + return ; + } + return ; +} + +export function DataDimensionEditor( + props: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + } ) { const { state, setState, layerId, accessor } = props; const index = state.layers.findIndex((l) => l.layerId === layerId); @@ -79,13 +100,30 @@ export function DimensionEditor( [accessor, index, localState, layer, setLocalState] ); - if (isAnnotationsLayer(layer)) { - return ; - } + const overwriteColor = getSeriesColor(layer, accessor); + const assignedColor = useMemo(() => { + const sortedAccessors: string[] = getSortedAccessors( + props.frame.datasourceLayers[layer.layerId] ?? layer.accessors, + layer + ); + const colorAssignments = getColorAssignments( + getDataLayers(state.layers), + { tables: props.frame.activeData ?? {} }, + props.formatFactory + ); - if (isReferenceLayer(layer)) { - return ; - } + return getAssignedColorConfig( + { + ...layer, + accessors: sortedAccessors.filter((sorted) => layer.accessors.includes(sorted)), + }, + accessor, + colorAssignments, + props.frame, + + props.paletteService + ).color; + }, [props.frame, props.paletteService, state.layers, accessor, props.formatFactory, layer]); const localLayer: XYDataLayerConfig = layer; if (props.groupId === 'breakdown') { @@ -114,6 +152,8 @@ export function DimensionEditor( <> diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx index e25c191d2bec1..8f9088f61b527 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_config_panel/reference_line_panel.tsx @@ -94,6 +94,7 @@ export const ReferenceLinePanel = ( { + const original = jest.requireActual('lodash'); + + return { + ...original, + debounce: (fn: unknown) => fn, + }; +}); describe('XY Config panels', () => { let frame: FramePublicAPI; @@ -343,5 +353,67 @@ describe('XY Config panels', () => { expect(component.find(EuiColorPicker).prop('color')).toEqual('red'); }); + test('does not apply incorrect color', () => { + const setState = jest.fn(); + const state = { + ...testState(), + layers: [ + { + seriesType: 'bar', + layerType: layerTypes.DATA, + layerId: 'first', + splitAccessor: undefined, + xAccessor: 'foo', + accessors: ['bar'], + yConfig: [{ forAccessor: 'bar', color: 'red' }], + }, + ], + } as XYState; + + const component = mount( + + ); + + act(() => { + component + .find('input[data-test-subj="euiColorPickerAnchor indexPattern-dimension-colorPicker"]') + .simulate('change', { + target: { value: 'INCORRECT_COLOR' }, + }); + }); + component.update(); + expect(component.find(EuiColorPicker).prop('color')).toEqual('INCORRECT_COLOR'); + expect(setState).not.toHaveBeenCalled(); + + act(() => { + component + .find('input[data-test-subj="euiColorPickerAnchor indexPattern-dimension-colorPicker"]') + .simulate('change', { + target: { value: '666666' }, + }); + }); + component.update(); + expect(component.find(EuiColorPicker).prop('color')).toEqual('666666'); + expect(setState).toHaveBeenCalled(); + }); }); }); From b077e75ddbf5ea4e79b616ebd5ae9e6940899af0 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 9 Jun 2022 08:36:24 -0400 Subject: [PATCH 30/44] [Response Ops] Fixing scripted metric agg in actions telemetry (#133612) * Adding try/catch around all actions telemetry queries * Fixing reduce script for actions telemetry * Fixing functional test * Reverting change Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/usage/actions_telemetry.test.ts | 78 ++- .../actions/server/usage/actions_telemetry.ts | 565 ++++++++++-------- x-pack/plugins/actions/server/usage/task.ts | 6 +- .../tests/telemetry/actions_telemetry.ts | 30 +- .../es_archives/event_log_telemetry/data.json | 29 + 5 files changed, 415 insertions(+), 293 deletions(-) diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts index ef4b7f0abd7fa..9fff9f9299ead 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -7,8 +7,11 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '@kbn/core/server/elasticsearch/client/mocks'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; import { getExecutionsPerDayCount, getInUseTotalCount, getTotalCount } from './actions_telemetry'; +const mockLogger = loggingSystemMock.create().get(); + describe('actions telemetry', () => { test('getTotalCount should replace first symbol . to __ for action types names', async () => { const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; @@ -97,7 +100,7 @@ describe('actions telemetry', () => { }, } ); - const telemetry = await getTotalCount(mockEsClient, 'test'); + const telemetry = await getTotalCount(mockEsClient, 'test', mockLogger); expect(mockEsClient.search).toHaveBeenCalledTimes(1); @@ -114,6 +117,24 @@ Object { `); }); + test('getTotalCount should return empty results if query throws error', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockRejectedValue(new Error('oh no')); + + const telemetry = await getTotalCount(mockEsClient, 'test', mockLogger); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + `Error executing actions telemetry task: getTotalCount - {}` + ); + expect(telemetry).toMatchInlineSnapshot(` +Object { + "countByType": Object {}, + "countTotal": 0, +} +`); + }); + test('getInUseTotalCount', async () => { const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; mockEsClient.search.mockResponseOnce( @@ -161,7 +182,7 @@ Object { ], }, }); - const telemetry = await getInUseTotalCount(mockEsClient, 'test'); + const telemetry = await getInUseTotalCount(mockEsClient, 'test', mockLogger); expect(mockEsClient.search).toHaveBeenCalledTimes(2); expect(telemetry).toMatchInlineSnapshot(` @@ -238,7 +259,7 @@ Object { ], }, }); - const telemetry = await getInUseTotalCount(mockEsClient, 'test', undefined, [ + const telemetry = await getInUseTotalCount(mockEsClient, 'test', mockLogger, undefined, [ { id: 'test', actionTypeId: '.email', @@ -279,6 +300,27 @@ Object { `); }); + test('getInUseTotalCount should return empty results if query throws error', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockRejectedValue(new Error('oh no')); + + const telemetry = await getInUseTotalCount(mockEsClient, 'test', mockLogger); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + `Error executing actions telemetry task: getInUseTotalCount - {}` + ); + expect(telemetry).toMatchInlineSnapshot(` +Object { + "countByAlertHistoryConnectorType": 0, + "countByType": Object {}, + "countEmailByService": Object {}, + "countNamespaces": 0, + "countTotal": 0, +} +`); + }); + test('getTotalCount accounts for preconfigured connectors', async () => { const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; mockEsClient.search.mockResponse( @@ -366,7 +408,7 @@ Object { }, } ); - const telemetry = await getTotalCount(mockEsClient, 'test', [ + const telemetry = await getTotalCount(mockEsClient, 'test', mockLogger, [ { id: 'test', actionTypeId: '.test', @@ -479,7 +521,7 @@ Object { ], }, }); - const telemetry = await getInUseTotalCount(mockEsClient, 'test', undefined, [ + const telemetry = await getInUseTotalCount(mockEsClient, 'test', mockLogger, undefined, [ { id: 'anotherServerLog', actionTypeId: '.server-log', @@ -587,7 +629,7 @@ Object { ], }, }); - const telemetry = await getInUseTotalCount(mockEsClient, 'test'); + const telemetry = await getInUseTotalCount(mockEsClient, 'test', mockLogger); expect(mockEsClient.search).toHaveBeenCalledTimes(2); expect(telemetry).toMatchInlineSnapshot(` @@ -683,7 +725,7 @@ Object { }, } ); - const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test'); + const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test', mockLogger); expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(telemetry).toStrictEqual({ @@ -705,4 +747,26 @@ Object { countTotal: 120, }); }); + + test('getExecutionsPerDayCount should return empty results if query throws error', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockRejectedValue(new Error('oh no')); + + const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test', mockLogger); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + `Error executing actions telemetry task: getExecutionsPerDayCount - {}` + ); + expect(telemetry).toMatchInlineSnapshot(` +Object { + "avgExecutionTime": 0, + "avgExecutionTimeByType": Object {}, + "countByType": Object {}, + "countFailed": 0, + "countFailedByType": Object {}, + "countTotal": 0, +} +`); + }); }); diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index ce3e69139d6e3..82f65ddf0cf8a 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -6,13 +6,14 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { ElasticsearchClient } from '@kbn/core/server'; +import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { AlertHistoryEsIndexConnectorId } from '../../common'; import { ActionResult, PreConfiguredAction } from '../types'; export async function getTotalCount( esClient: ElasticsearchClient, kibanaIndex: string, + logger: Logger, preconfiguredActions?: PreConfiguredAction[] ) { const scriptedMetric = { @@ -28,62 +29,75 @@ export async function getTotalCount( // Reduce script is executed across all clusters, so we need to add up all the total from each cluster // This also needs to account for having no data reduce_script: ` - Map result = [:]; - for (Map m : states.toArray()) { - if (m !== null) { - for (String k : m.keySet()) { - result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); - } + HashMap result = new HashMap(); + HashMap combinedTypes = new HashMap(); + for (state in states) { + for (String type : state.types.keySet()) { + int typeCount = combinedTypes.containsKey(type) ? combinedTypes.get(type) + state.types.get(type) : state.types.get(type); + combinedTypes.put(type, typeCount); } } + + result.types = combinedTypes; return result; `, }, }; - const searchResult = await esClient.search({ - index: kibanaIndex, - size: 0, - body: { - query: { - bool: { - filter: [{ term: { type: 'action' } }], + try { + const searchResult = await esClient.search({ + index: kibanaIndex, + size: 0, + body: { + query: { + bool: { + filter: [{ term: { type: 'action' } }], + }, + }, + aggs: { + byActionTypeId: scriptedMetric, }, }, - aggs: { - byActionTypeId: scriptedMetric, - }, - }, - }); - // @ts-expect-error aggegation type is not specified - const aggs = searchResult.aggregations?.byActionTypeId.value?.types; - const countByType = Object.keys(aggs).reduce( - // ES DSL aggregations are returned as `any` by esClient.search - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (obj: any, key: string) => ({ - ...obj, - [replaceFirstAndLastDotSymbols(key)]: aggs[key], - }), - {} - ); - if (preconfiguredActions && preconfiguredActions.length) { - for (const preconfiguredAction of preconfiguredActions) { - const actionTypeId = replaceFirstAndLastDotSymbols(preconfiguredAction.actionTypeId); - countByType[actionTypeId] = countByType[actionTypeId] || 0; - countByType[actionTypeId]++; + }); + // @ts-expect-error aggegation type is not specified + const aggs = searchResult.aggregations?.byActionTypeId.value?.types; + const countByType = Object.keys(aggs).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: aggs[key], + }), + {} + ); + if (preconfiguredActions && preconfiguredActions.length) { + for (const preconfiguredAction of preconfiguredActions) { + const actionTypeId = replaceFirstAndLastDotSymbols(preconfiguredAction.actionTypeId); + countByType[actionTypeId] = countByType[actionTypeId] || 0; + countByType[actionTypeId]++; + } } + return { + countTotal: + Object.keys(aggs).reduce( + (total: number, key: string) => parseInt(aggs[key], 10) + total, + 0 + ) + (preconfiguredActions?.length ?? 0), + countByType, + }; + } catch (err) { + logger.warn(`Error executing actions telemetry task: getTotalCount - ${JSON.stringify(err)}`); + return { + countTotal: 0, + countByType: {}, + }; } - return { - countTotal: - Object.keys(aggs).reduce((total: number, key: string) => parseInt(aggs[key], 10) + total, 0) + - (preconfiguredActions?.length ?? 0), - countByType, - }; } export async function getInUseTotalCount( esClient: ElasticsearchClient, kibanaIndex: string, + logger: Logger, referenceType?: string, preconfiguredActions?: PreConfiguredAction[] ): Promise<{ @@ -223,142 +237,156 @@ export async function getInUseTotalCount( }); } - const actionResults = await esClient.search({ - index: kibanaIndex, - size: 0, - body: { - query: { - bool: { - filter: { - bool: { - must_not: { - term: { - type: 'action_task_params', + try { + const actionResults = await esClient.search({ + index: kibanaIndex, + size: 0, + body: { + query: { + bool: { + filter: { + bool: { + must_not: { + term: { + type: 'action_task_params', + }, }, + must: mustQuery, }, - must: mustQuery, }, }, }, - }, - aggs: { - refs: { - nested: { - path: 'references', - }, - aggs: { - actionRefIds: scriptedMetric, - }, - }, - preconfigured_actions: { - nested: { - path: 'alert.actions', + aggs: { + refs: { + nested: { + path: 'references', + }, + aggs: { + actionRefIds: scriptedMetric, + }, }, - aggs: { - preconfiguredActionRefIds: preconfiguredActionsScriptedMetric, + preconfigured_actions: { + nested: { + path: 'alert.actions', + }, + aggs: { + preconfiguredActionRefIds: preconfiguredActionsScriptedMetric, + }, }, }, }, - }, - }); + }); - // @ts-expect-error aggegation type is not specified - const aggs = actionResults.aggregations.refs.actionRefIds.value; - const preconfiguredActionsAggs = // @ts-expect-error aggegation type is not specified - actionResults.aggregations.preconfigured_actions?.preconfiguredActionRefIds.value; + const aggs = actionResults.aggregations.refs.actionRefIds.value; + const preconfiguredActionsAggs = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.preconfigured_actions?.preconfiguredActionRefIds.value; - const { hits: actions } = await esClient.search<{ - action: ActionResult; - namespaces: string[]; - }>({ - index: kibanaIndex, - _source_includes: ['action', 'namespaces'], - body: { - query: { - bool: { - must: [ - { - term: { type: 'action' }, - }, - { - terms: { - _id: Object.entries(aggs.connectorIds).map(([key]) => `action:${key}`), + const { hits: actions } = await esClient.search<{ + action: ActionResult; + namespaces: string[]; + }>({ + index: kibanaIndex, + _source_includes: ['action', 'namespaces'], + body: { + query: { + bool: { + must: [ + { + term: { type: 'action' }, }, - }, - ], + { + terms: { + _id: Object.entries(aggs.connectorIds).map(([key]) => `action:${key}`), + }, + }, + ], + }, }, }, - }, - }); + }); - const countByActionTypeId = actions.hits.reduce( - (actionTypeCount: Record, action) => { - const actionSource = action._source!; - const alertTypeId = replaceFirstAndLastDotSymbols(actionSource.action.actionTypeId); - const currentCount = - actionTypeCount[alertTypeId] !== undefined ? actionTypeCount[alertTypeId] : 0; - actionTypeCount[alertTypeId] = currentCount + 1; - return actionTypeCount; - }, - {} - ); + const countByActionTypeId = actions.hits.reduce( + (actionTypeCount: Record, action) => { + const actionSource = action._source!; + const alertTypeId = replaceFirstAndLastDotSymbols(actionSource.action.actionTypeId); + const currentCount = + actionTypeCount[alertTypeId] !== undefined ? actionTypeCount[alertTypeId] : 0; + actionTypeCount[alertTypeId] = currentCount + 1; + return actionTypeCount; + }, + {} + ); - const namespacesList = actions.hits.reduce((_namespaces: Set, action) => { - const namespaces = action._source?.namespaces ?? ['default']; - namespaces.forEach((namespace) => { - if (!_namespaces.has(namespace)) { - _namespaces.add(namespace); - } - }); - return _namespaces; - }, new Set()); + const namespacesList = actions.hits.reduce((_namespaces: Set, action) => { + const namespaces = action._source?.namespaces ?? ['default']; + namespaces.forEach((namespace) => { + if (!_namespaces.has(namespace)) { + _namespaces.add(namespace); + } + }); + return _namespaces; + }, new Set()); - const countEmailByService = actions.hits - .filter((action) => action._source!.action.actionTypeId === '.email') - .reduce((emailServiceCount: Record, action) => { - const service = (action._source!.action.config?.service ?? 'other') as string; - const currentCount = - emailServiceCount[service] !== undefined ? emailServiceCount[service] : 0; - emailServiceCount[service] = currentCount + 1; - return emailServiceCount; - }, {}); + const countEmailByService = actions.hits + .filter((action) => action._source!.action.actionTypeId === '.email') + .reduce((emailServiceCount: Record, action) => { + const service = (action._source!.action.config?.service ?? 'other') as string; + const currentCount = + emailServiceCount[service] !== undefined ? emailServiceCount[service] : 0; + emailServiceCount[service] = currentCount + 1; + return emailServiceCount; + }, {}); - let preconfiguredAlertHistoryConnectors = 0; - const preconfiguredActionsRefs: Array<{ - actionTypeId: string; - actionRef: string; - }> = preconfiguredActionsAggs ? Object.values(preconfiguredActionsAggs?.actionRefs) : []; - for (const { actionRef, actionTypeId: rawActionTypeId } of preconfiguredActionsRefs) { - const actionTypeId = replaceFirstAndLastDotSymbols(rawActionTypeId); - countByActionTypeId[actionTypeId] = countByActionTypeId[actionTypeId] || 0; - countByActionTypeId[actionTypeId]++; - if (actionRef === `preconfigured:${AlertHistoryEsIndexConnectorId}`) { - preconfiguredAlertHistoryConnectors++; - } - if (preconfiguredActions && actionTypeId === '__email') { - const preconfiguredConnectorId = actionRef.split(':')[1]; - const service = (preconfiguredActions.find( - (preconfConnector) => preconfConnector.id === preconfiguredConnectorId - )?.config?.service ?? 'other') as string; - const currentCount = - countEmailByService[service] !== undefined ? countEmailByService[service] : 0; - countEmailByService[service] = currentCount + 1; + let preconfiguredAlertHistoryConnectors = 0; + const preconfiguredActionsRefs: Array<{ + actionTypeId: string; + actionRef: string; + }> = preconfiguredActionsAggs ? Object.values(preconfiguredActionsAggs?.actionRefs) : []; + for (const { actionRef, actionTypeId: rawActionTypeId } of preconfiguredActionsRefs) { + const actionTypeId = replaceFirstAndLastDotSymbols(rawActionTypeId); + countByActionTypeId[actionTypeId] = countByActionTypeId[actionTypeId] || 0; + countByActionTypeId[actionTypeId]++; + if (actionRef === `preconfigured:${AlertHistoryEsIndexConnectorId}`) { + preconfiguredAlertHistoryConnectors++; + } + if (preconfiguredActions && actionTypeId === '__email') { + const preconfiguredConnectorId = actionRef.split(':')[1]; + const service = (preconfiguredActions.find( + (preconfConnector) => preconfConnector.id === preconfiguredConnectorId + )?.config?.service ?? 'other') as string; + const currentCount = + countEmailByService[service] !== undefined ? countEmailByService[service] : 0; + countEmailByService[service] = currentCount + 1; + } } - } - return { - countTotal: aggs.total + (preconfiguredActionsAggs?.total ?? 0), - countByType: countByActionTypeId, - countByAlertHistoryConnectorType: preconfiguredAlertHistoryConnectors, - countEmailByService, - countNamespaces: namespacesList.size, - }; + return { + countTotal: aggs.total + (preconfiguredActionsAggs?.total ?? 0), + countByType: countByActionTypeId, + countByAlertHistoryConnectorType: preconfiguredAlertHistoryConnectors, + countEmailByService, + countNamespaces: namespacesList.size, + }; + } catch (err) { + logger.warn( + `Error executing actions telemetry task: getInUseTotalCount - ${JSON.stringify(err)}` + ); + return { + countTotal: 0, + countByType: {}, + countByAlertHistoryConnectorType: 0, + countEmailByService: {}, + countNamespaces: 0, + }; + } } export async function getInUseByAlertingTotalCounts( esClient: ElasticsearchClient, kibanaIndex: string, + logger: Logger, preconfiguredActions?: PreConfiguredAction[] ): Promise<{ countTotal: number; @@ -367,7 +395,7 @@ export async function getInUseByAlertingTotalCounts( countEmailByService: Record; countNamespaces: number; }> { - return await getInUseTotalCount(esClient, kibanaIndex, 'alert', preconfiguredActions); + return await getInUseTotalCount(esClient, kibanaIndex, logger, 'alert', preconfiguredActions); } function replaceFirstAndLastDotSymbols(strToReplace: string) { @@ -379,7 +407,8 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) { export async function getExecutionsPerDayCount( esClient: ElasticsearchClient, - eventLogIndex: string + eventLogIndex: string, + logger: Logger ): Promise<{ countTotal: number; countByType: Record; @@ -422,83 +451,85 @@ export async function getExecutionsPerDayCount( }, }; - const actionResults = await esClient.search({ - index: eventLogIndex, - size: 0, - body: { - query: { - bool: { - filter: { - bool: { - must: [ - { - term: { 'event.action': 'execute' }, - }, - { - term: { 'event.provider': 'actions' }, - }, - { - range: { - '@timestamp': { - gte: 'now-1d', + try { + const actionResults = await esClient.search({ + index: eventLogIndex, + size: 0, + body: { + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { 'event.action': 'execute' }, + }, + { + term: { 'event.provider': 'actions' }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, }, }, - }, - ], + ], + }, }, }, }, - }, - aggs: { - totalExecutions: { - nested: { - path: 'kibana.saved_objects', - }, - aggs: { - byConnectorTypeId: scriptedMetric, - }, - }, - failedExecutions: { - filter: { - bool: { - filter: [ - { - term: { - 'event.outcome': 'failure', - }, - }, - ], + aggs: { + totalExecutions: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + byConnectorTypeId: scriptedMetric, }, }, - aggs: { - refs: { - nested: { - path: 'kibana.saved_objects', + failedExecutions: { + filter: { + bool: { + filter: [ + { + term: { + 'event.outcome': 'failure', + }, + }, + ], }, - aggs: { - byConnectorTypeId: scriptedMetric, + }, + aggs: { + refs: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + byConnectorTypeId: scriptedMetric, + }, }, }, }, - }, - avgDuration: { avg: { field: 'event.duration' } }, - avgDurationByType: { - nested: { - path: 'kibana.saved_objects', - }, - aggs: { - actionSavedObjects: { - filter: { term: { 'kibana.saved_objects.type': 'action' } }, - aggs: { - byTypeId: { - terms: { - field: 'kibana.saved_objects.type_id', - }, - aggs: { - refs: { - reverse_nested: {}, - aggs: { - avgDuration: { avg: { field: 'event.duration' } }, + avgDuration: { avg: { field: 'event.duration' } }, + avgDurationByType: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + actionSavedObjects: { + filter: { term: { 'kibana.saved_objects.type': 'action' } }, + aggs: { + byTypeId: { + terms: { + field: 'kibana.saved_objects.type_id', + }, + aggs: { + refs: { + reverse_nested: {}, + aggs: { + avgDuration: { avg: { field: 'event.duration' } }, + }, }, }, }, @@ -508,53 +539,65 @@ export async function getExecutionsPerDayCount( }, }, }, - }, - }); - - // @ts-expect-error aggegation type is not specified - const aggsExecutions = actionResults.aggregations.totalExecutions?.byConnectorTypeId.value; - // convert nanoseconds to milliseconds - const aggsAvgExecutionTime = Math.round( - // @ts-expect-error aggegation type is not specified - actionResults.aggregations.avgDuration.value / (1000 * 1000) - ); - const aggsFailedExecutions = - // @ts-expect-error aggegation type is not specified - actionResults.aggregations.failedExecutions?.refs?.byConnectorTypeId.value; + }); - const avgDurationByType = // @ts-expect-error aggegation type is not specified - actionResults.aggregations.avgDurationByType?.actionSavedObjects?.byTypeId?.buckets; + const aggsExecutions = actionResults.aggregations.totalExecutions?.byConnectorTypeId.value; + // convert nanoseconds to milliseconds + const aggsAvgExecutionTime = Math.round( + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.avgDuration.value / (1000 * 1000) + ); + const aggsFailedExecutions = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.failedExecutions?.refs?.byConnectorTypeId.value; - const avgExecutionTimeByType: Record = avgDurationByType.reduce( - // @ts-expect-error aggegation type is not specified - (res: Record, bucket) => { - res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket?.refs.avgDuration.value; - return res; - }, - {} - ); + const avgDurationByType = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.avgDurationByType?.actionSavedObjects?.byTypeId?.buckets; - return { - countTotal: aggsExecutions.total, - countByType: Object.entries(aggsExecutions.connectorTypes).reduce( - (res: Record, [key, value]) => { - // @ts-expect-error aggegation type is not specified - res[replaceFirstAndLastDotSymbols(key)] = value; - return res; - }, - {} - ), - countFailed: aggsFailedExecutions.total, - countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce( - (res: Record, [key, value]) => { - // @ts-expect-error aggegation type is not specified - res[replaceFirstAndLastDotSymbols(key)] = value; + const avgExecutionTimeByType: Record = avgDurationByType.reduce( + // @ts-expect-error aggegation type is not specified + (res: Record, bucket) => { + res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket?.refs.avgDuration.value; return res; }, {} - ), - avgExecutionTime: aggsAvgExecutionTime, - avgExecutionTimeByType, - }; + ); + + return { + countTotal: aggsExecutions.total, + countByType: Object.entries(aggsExecutions.connectorTypes).reduce( + (res: Record, [key, value]) => { + // @ts-expect-error aggegation type is not specified + res[replaceFirstAndLastDotSymbols(key)] = value; + return res; + }, + {} + ), + countFailed: aggsFailedExecutions.total, + countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce( + (res: Record, [key, value]) => { + // @ts-expect-error aggegation type is not specified + res[replaceFirstAndLastDotSymbols(key)] = value; + return res; + }, + {} + ), + avgExecutionTime: aggsAvgExecutionTime, + avgExecutionTimeByType, + }; + } catch (err) { + logger.warn( + `Error executing actions telemetry task: getExecutionsPerDayCount - ${JSON.stringify(err)}` + ); + return { + countTotal: 0, + countByType: {}, + countFailed: 0, + countFailedByType: {}, + avgExecutionTime: 0, + avgExecutionTimeByType: {}, + }; + } } diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index 5591a817bd598..68b109ec8b0fa 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -98,9 +98,9 @@ export function telemetryTaskRunner( async run() { const esClient = await getEsClient(); return Promise.all([ - getTotalCount(esClient, kibanaIndex, preconfiguredActions), - getInUseTotalCount(esClient, kibanaIndex, undefined, preconfiguredActions), - getExecutionsPerDayCount(esClient, eventLogIndex), + getTotalCount(esClient, kibanaIndex, logger, preconfiguredActions), + getInUseTotalCount(esClient, kibanaIndex, logger, undefined, preconfiguredActions), + getExecutionsPerDayCount(esClient, eventLogIndex, logger), ]) .then(([totalAggegations, totalInUse, totalExecutionsPerDay]) => { return { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/actions_telemetry.ts index b1e77b98b792d..c8a4bf1ae6540 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/actions_telemetry.ts @@ -7,14 +7,9 @@ import expect from '@kbn/expect'; import { Spaces, Superuser } from '../../../scenarios'; -import { - getUrlPrefix, - getEventLog, - getTestRuleData, - ObjectRemover, - TaskManagerDoc, -} from '../../../../common/lib'; +import { getUrlPrefix, getEventLog, getTestRuleData, TaskManagerDoc } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { setupSpacesAndUsers } from '../../../setup'; // eslint-disable-next-line import/no-default-export export default function createActionsTelemetryTests({ getService }: FtrProviderContext) { @@ -22,26 +17,19 @@ export default function createActionsTelemetryTests({ getService }: FtrProviderC const es = getService('es'); const retry = getService('retry'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); describe('actions telemetry', () => { const alwaysFiringRuleId: { [key: string]: string } = {}; - const objectRemover = new ObjectRemover(supertest); before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/event_log_telemetry'); // reset the state in the telemetry task - await es.update({ - id: `task:Actions-actions_telemetry`, - index: '.kibana_task_manager', - body: { - doc: { - task: { - state: '{}', - }, - }, - }, - }); + await setupSpacesAndUsers(getService); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/event_log_telemetry'); }); - after(() => objectRemover.removeAll()); async function createConnector(opts: { name: string; space: string; connectorTypeId: string }) { const { name, space, connectorTypeId } = opts; @@ -56,7 +44,6 @@ export default function createActionsTelemetryTests({ getService }: FtrProviderC secrets: {}, }) .expect(200); - objectRemover.add(space, createdConnector.id, 'action', 'actions'); return createdConnector.id; } @@ -68,7 +55,6 @@ export default function createActionsTelemetryTests({ getService }: FtrProviderC .auth(Superuser.username, Superuser.password) .send(getTestRuleData(ruleOverwrites)); expect(ruleResponse.status).to.eql(200); - objectRemover.add(space, ruleResponse.body.id, 'rule', 'alerting'); return ruleResponse.body.id; } diff --git a/x-pack/test/functional/es_archives/event_log_telemetry/data.json b/x-pack/test/functional/es_archives/event_log_telemetry/data.json index 92473f44b53de..55bc3aee1d272 100644 --- a/x-pack/test/functional/es_archives/event_log_telemetry/data.json +++ b/x-pack/test/functional/es_archives/event_log_telemetry/data.json @@ -27,6 +27,35 @@ } } +{ + "type": "doc", + "value": { + "id": "task:Actions-actions_telemetry", + "index": ".kibana_task_manager_1", + "source": { + "migrationVersion": { + "task": "8.2.0" + }, + "references": [ + ], + "task": { + "attempts": 0, + "params": "{}", + "retryAt": null, + "runAt": "2020-09-04T11:51:05.197Z", + "scheduledAt": "2020-09-04T11:51:05.197Z", + "startedAt": null, + "state": "{}", + "ownerId": null, + "status": "idle", + "taskType": "actions_telemetry" + }, + "type": "task", + "updated_at": "2020-09-04T11:51:05.197Z" + } + } +} + { "type": "doc", "value": { From a3268dece2a17b6a4113ddce70a88d958b9cd4f8 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Thu, 9 Jun 2022 14:39:39 +0200 Subject: [PATCH 31/44] [APM] Fix apm e2e tests (#133941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix integration policy e2e test * Skip integration e2e * Use type-only import for synthtrace package * Fix synthrace script * Revert "Skip integration e2e" This reverts commit a5992d5ba9f1ee9309d2852e1963f10da487130f. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Søren Louv-Jansen Co-authored-by: Dario Gieselaar --- .../elastic-apm-synthtrace/bin/synthtrace | 2 +- packages/elastic-apm-synthtrace/src/cli.ts | 9 ++++ packages/elastic-apm-synthtrace/src/index.ts | 1 - scripts/synthtrace.js | 2 +- .../integration_policy.spec.ts | 48 ------------------- x-pack/plugins/apm/ftr_e2e/synthtrace.ts | 2 +- 6 files changed, 12 insertions(+), 52 deletions(-) create mode 100644 packages/elastic-apm-synthtrace/src/cli.ts diff --git a/packages/elastic-apm-synthtrace/bin/synthtrace b/packages/elastic-apm-synthtrace/bin/synthtrace index a9d011b961bc6..0d066ab33258c 100755 --- a/packages/elastic-apm-synthtrace/bin/synthtrace +++ b/packages/elastic-apm-synthtrace/bin/synthtrace @@ -14,4 +14,4 @@ require('@babel/register')({ presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], }); -require('../src/scripts/run_synthtrace').runSynthtrace(); +require('../src/cli').runSynthtrace(); diff --git a/packages/elastic-apm-synthtrace/src/cli.ts b/packages/elastic-apm-synthtrace/src/cli.ts new file mode 100644 index 0000000000000..efd8309c737e1 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/cli.ts @@ -0,0 +1,9 @@ +/* + * 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 { runSynthtrace } from './scripts/run_synthtrace'; diff --git a/packages/elastic-apm-synthtrace/src/index.ts b/packages/elastic-apm-synthtrace/src/index.ts index e2ffe2906fa26..3e7a2f1d59190 100644 --- a/packages/elastic-apm-synthtrace/src/index.ts +++ b/packages/elastic-apm-synthtrace/src/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export { runSynthtrace } from './scripts/run_synthtrace'; export { timerange } from './lib/timerange'; export { apm } from './lib/apm'; export { stackMonitoring } from './lib/stack_monitoring'; diff --git a/scripts/synthtrace.js b/scripts/synthtrace.js index 6cbfd2334d6e1..5e551acf84769 100644 --- a/scripts/synthtrace.js +++ b/scripts/synthtrace.js @@ -13,4 +13,4 @@ require('../src/setup_node_env'); // compile scenarios with `yarn kbn bootstrap` every time scenario changes. // eslint-disable-next-line @kbn/imports/uniform_imports -require('../packages/elastic-apm-synthtrace/src/index').runSynthtrace(); +require('../packages/elastic-apm-synthtrace/src/cli').runSynthtrace(); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts index 75c69e036361a..f87d9c47e6624 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/integration_settings/integration_policy.spec.ts @@ -78,54 +78,6 @@ describe('when navigating to integration page', () => { }); }); - it('adds a new policy without agent', () => { - apisToIntercept.map(({ endpoint, method, name }) => { - cy.intercept(method, endpoint).as(name); - }); - - cy.url().should('include', 'app/fleet/integrations/apm/add-integration'); - policyFormFields.map((field) => { - cy.get(`[data-test-subj="${field.selector}"`).clear().type(field.value); - }); - cy.contains('Save and continue').click(); - cy.wait('@fleetAgentPolicies'); - cy.wait('@fleetAgentStatus'); - cy.wait('@fleetPackagePolicies'); - - cy.get('[data-test-subj="confirmModalCancelButton').click(); - - cy.url().should('include', '/app/integrations/detail/apm/policies'); - cy.contains(policyName); - }); - - it('updates an existing policy', () => { - apisToIntercept.map(({ endpoint, method, name }) => { - cy.intercept(method, endpoint).as(name); - }); - - policyFormFields.map((field) => { - cy.get(`[data-test-subj="${field.selector}"`) - .clear() - .type(`${field.value}-new`); - }); - - cy.contains('Save and continue').click(); - cy.wait('@fleetAgentPolicies'); - cy.wait('@fleetAgentStatus'); - cy.wait('@fleetPackagePolicies'); - - cy.get('[data-test-subj="confirmModalCancelButton').click(); - cy.contains(`${policyName}-new`).click(); - - policyFormFields.map((field) => { - cy.get(`[data-test-subj="${field.selector}"`) - .clear() - .type(`${field.value}-updated`); - }); - cy.contains('Save integration').click(); - cy.contains(`${policyName}-updated`); - }); - it('should display Tail-based section on latest version', () => { cy.visit('/app/fleet/integrations/apm/add-integration'); cy.contains('Tail-based sampling').should('exist'); diff --git a/x-pack/plugins/apm/ftr_e2e/synthtrace.ts b/x-pack/plugins/apm/ftr_e2e/synthtrace.ts index 1d4559bf174e6..de113c6c44c4d 100644 --- a/x-pack/plugins/apm/ftr_e2e/synthtrace.ts +++ b/x-pack/plugins/apm/ftr_e2e/synthtrace.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EntityIterable } from '@elastic/apm-synthtrace'; +import type { EntityIterable } from '@elastic/apm-synthtrace'; export const synthtrace = { index: (events: EntityIterable) => From 05d376184fc899bcf6c4f171ea232e2c4e04b766 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 9 Jun 2022 09:58:49 -0400 Subject: [PATCH 32/44] skip failing test suite (#126873) --- .../apps/triggers_actions_ui/alert_create_flyout.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index 0ee1694d340d8..b51b889bf0437 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -112,7 +112,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.missingOrFail('confirmRuleCloseModal'); } - describe('create alert', function () { + // Failing: See https://github.com/elastic/kibana/issues/126873 + describe.skip('create alert', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('rulesTab'); From 96f31938fa0d87536739fd8c606380679a4a9327 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+dimaanj@users.noreply.github.com> Date: Thu, 9 Jun 2022 17:04:35 +0300 Subject: [PATCH 33/44] [Discover] Fix context page with special char in data view (#133222) * [Discover] fix context page with special char in data view * [Discover] fix linting * [Discover] add test * [Discover] apply suggestions Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/context/context_app_route.tsx | 13 +++++++------ .../public/application/doc/single_doc_route.tsx | 4 ++-- ...url_param.ts => _context_encoded_url_params.ts} | 14 +++++++++++--- test/functional/apps/discover/index.ts | 2 +- test/functional/page_objects/settings_page.ts | 12 ++++++++++++ 5 files changed, 33 insertions(+), 12 deletions(-) rename test/functional/apps/discover/{_context_encoded_url_param.ts => _context_encoded_url_params.ts} (84%) diff --git a/src/plugins/discover/public/application/context/context_app_route.tsx b/src/plugins/discover/public/application/context/context_app_route.tsx index 5bbbefa5cdb93..313769d98ae49 100644 --- a/src/plugins/discover/public/application/context/context_app_route.tsx +++ b/src/plugins/discover/public/application/context/context_app_route.tsx @@ -28,6 +28,7 @@ export function ContextAppRoute() { const { indexPatternId, id } = useParams(); const anchorId = decodeURIComponent(id); + const dataViewId = decodeURIComponent(indexPatternId); const breadcrumb = useMainRouteBreadcrumb(); useEffect(() => { @@ -41,7 +42,7 @@ export function ContextAppRoute() { ]); }, [chrome, breadcrumb]); - const { indexPattern, error } = useIndexPattern(services.indexPatterns, indexPatternId); + const { indexPattern, error } = useIndexPattern(services.indexPatterns, dataViewId); if (error) { return ( @@ -50,15 +51,15 @@ export function ContextAppRoute() { iconColor="danger" title={ } body={ } /> diff --git a/src/plugins/discover/public/application/doc/single_doc_route.tsx b/src/plugins/discover/public/application/doc/single_doc_route.tsx index 8ffb6f5d71fb3..c03ec2658c86d 100644 --- a/src/plugins/discover/public/application/doc/single_doc_route.tsx +++ b/src/plugins/discover/public/application/doc/single_doc_route.tsx @@ -67,13 +67,13 @@ const SingleDoc = ({ id }: SingleDocRouteProps) => { title={ } body={ } diff --git a/test/functional/apps/discover/_context_encoded_url_param.ts b/test/functional/apps/discover/_context_encoded_url_params.ts similarity index 84% rename from test/functional/apps/discover/_context_encoded_url_param.ts rename to test/functional/apps/discover/_context_encoded_url_params.ts index 5556362310066..293a1ee0b5e28 100644 --- a/test/functional/apps/discover/_context_encoded_url_param.ts +++ b/test/functional/apps/discover/_context_encoded_url_params.ts @@ -9,6 +9,9 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +const customDataViewIdParam = 'context-enc:oded-param'; +const customDocIdParam = '1+1=2'; + export default function ({ getService, getPageObjects }: FtrProviderContext) { const dataGrid = getService('dataGrid'); const security = getService('security'); @@ -21,19 +24,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await security.testUser.setRoles(['kibana_admin', 'context_encoded_param']); await PageObjects.common.navigateToApp('settings'); await es.transport.request({ - path: '/context-encoded-param/_doc/1+1=2', + path: `/context-encoded-param/_doc/${customDocIdParam}`, method: 'PUT', body: { username: 'Dmitry', '@timestamp': '2015-09-21T09:30:23', }, }); - await PageObjects.settings.createIndexPattern('context-encoded-param'); + await PageObjects.settings.createIndexPattern( + 'context-encoded-param', + '@timestamp', + true, + customDataViewIdParam + ); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - it('should navigate correctly', async () => { + it('should navigate correctly when ', async () => { await PageObjects.discover.selectIndexPattern('context-encoded-param'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitForDocTableLoadingComplete(); diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index 431d77f1b4b5e..44ad8687f8ed5 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -57,7 +57,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_date_nested')); loadTestFile(require.resolve('./_search_on_page_load')); loadTestFile(require.resolve('./_chart_hidden')); - loadTestFile(require.resolve('./_context_encoded_url_param')); + loadTestFile(require.resolve('./_context_encoded_url_params')); loadTestFile(require.resolve('./_data_view_editor')); } }); diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index ca1fe0d2b47ad..bd3e259ef6291 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -421,11 +421,20 @@ export class SettingsPageObject extends FtrService { } } + async addCustomDataViewId(value: string) { + await this.testSubjects.click('toggleAdvancedSetting'); + const customDataViewIdInput = await ( + await this.testSubjects.find('savedObjectIdField') + ).findByTagName('input'); + await customDataViewIdInput.type(value); + } + async createIndexPattern( indexPatternName: string, // null to bypass default value timefield: string | null = '@timestamp', isStandardIndexPattern = true, + customDataViewId?: string, dataViewName?: string ) { await this.retry.try(async () => { @@ -457,6 +466,9 @@ export class SettingsPageObject extends FtrService { if (timefield) { await this.selectTimeFieldOption(timefield); } + if (customDataViewId) { + await this.addCustomDataViewId(customDataViewId); + } if (dataViewName) { await this.setNameField(dataViewName); } From a2aca7fd899ff7c9c3739a9646e8eb2a93fcb41b Mon Sep 17 00:00:00 2001 From: Mat Schaffer Date: Thu, 9 Jun 2022 23:25:29 +0900 Subject: [PATCH 34/44] [Stack Monitoring] Add multiple persistent pipelines to logstash reference (#132870) --- x-pack/plugins/monitoring/dev_docs/how_to/local_setup.md | 6 +++--- x-pack/plugins/monitoring/dev_docs/reference/logstash.yml | 3 +++ .../plugins/monitoring/dev_docs/reference/pipelines.yml | 8 ++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/monitoring/dev_docs/reference/pipelines.yml diff --git a/x-pack/plugins/monitoring/dev_docs/how_to/local_setup.md b/x-pack/plugins/monitoring/dev_docs/how_to/local_setup.md index 04061468c6354..2abc96a5d4957 100644 --- a/x-pack/plugins/monitoring/dev_docs/how_to/local_setup.md +++ b/x-pack/plugins/monitoring/dev_docs/how_to/local_setup.md @@ -81,8 +81,8 @@ docker run --name logstash \ --hostname=logstash \ --publish=9600:9600 \ --volume="$(pwd)/x-pack/plugins/monitoring/dev_docs/reference/logstash.yml:/usr/share/logstash/config/logstash.yml:ro" \ - docker.elastic.co/logstash/logstash:master-SNAPSHOT \ - -e 'input { java_generator { eps => 1 } } output { file { path => "/dev/null" } }' + --volume="$(pwd)/x-pack/plugins/monitoring/dev_docs/reference/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro" \ + docker.elastic.co/logstash/logstash:master-SNAPSHOT ``` # Complete docker setup @@ -95,4 +95,4 @@ See (internal) https://github.com/elastic/observability-dev/tree/main/tools/dock For some types of changes (for example, new fields, templates, endpoints or data processing logic), you may want to run stack components from source. -See [Running Components from Source](running_components_from_source.md) for details on how to do this for each component. +See [Running Components from Source](running_components_from_source.md) for details on how to do this for each component. \ No newline at end of file diff --git a/x-pack/plugins/monitoring/dev_docs/reference/logstash.yml b/x-pack/plugins/monitoring/dev_docs/reference/logstash.yml index 4e2516133b130..ea8405d2f3cac 100644 --- a/x-pack/plugins/monitoring/dev_docs/reference/logstash.yml +++ b/x-pack/plugins/monitoring/dev_docs/reference/logstash.yml @@ -5,3 +5,6 @@ api.http.host: 0.0.0.0 # monitoring.elasticsearch.hosts: http://host.docker.internal:9200 # monitoring.elasticsearch.username: elastic # monitoring.elasticsearch.password: changeme + +queue.type: persisted +queue.max_bytes: 128mb diff --git a/x-pack/plugins/monitoring/dev_docs/reference/pipelines.yml b/x-pack/plugins/monitoring/dev_docs/reference/pipelines.yml new file mode 100644 index 0000000000000..89233d2fed76f --- /dev/null +++ b/x-pack/plugins/monitoring/dev_docs/reference/pipelines.yml @@ -0,0 +1,8 @@ +- pipeline.id: generator1 + config.string: | + input { java_generator { eps => 1 } } + output { file { path => "/dev/null" } } +- pipeline.id: generator5 + config.string: | + input { java_generator { eps => 5 } } + output { file { path => "/dev/null" } } From 5c166a698ce41171f8e4be6eff89654f1126b7fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jun 2022 07:39:35 -0700 Subject: [PATCH 35/44] Update dependency core-js to ^3.22.8 (main) (#133981) * Update dependency core-js to ^3.22.8 * dedupe Co-authored-by: Renovate Bot Co-authored-by: Jonathan Budzenski --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c46800c67cf54..5ea8bf01eb586 100644 --- a/package.json +++ b/package.json @@ -262,7 +262,7 @@ "constate": "^1.3.2", "content-disposition": "0.5.3", "copy-to-clipboard": "^3.0.8", - "core-js": "^3.22.7", + "core-js": "^3.22.8", "cronstrue": "^1.51.0", "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", diff --git a/yarn.lock b/yarn.lock index c140a0148c6b9..60575ef825057 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11299,10 +11299,10 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.9: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.4, core-js@^3.22.7, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: - version "3.22.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.7.tgz#8d6c37f630f6139b8732d10f2c114c3f1d00024f" - integrity sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg== +core-js@^3.0.4, core-js@^3.22.8, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: + version "3.22.8" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.8.tgz#23f860b1fe60797cc4f704d76c93fea8a2f60631" + integrity sha512-UoGQ/cfzGYIuiq6Z7vWL1HfkE9U9IZ4Ub+0XSiJTCzvbZzgPA69oDF2f+lgJ6dFFLEdjW5O6svvoKzXX23xFkA== core-util-is@1.0.2, core-util-is@^1.0.2, core-util-is@~1.0.0: version "1.0.2" From 580a964eda8af09df7cb4dbc92014edf8719f5c4 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 9 Jun 2022 07:47:56 -0700 Subject: [PATCH 36/44] [DOCS] Improve terminology in execute connector API (#133694) --- docs/api/actions-and-connectors.asciidoc | 2 +- .../actions-and-connectors/execute.asciidoc | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/api/actions-and-connectors.asciidoc b/docs/api/actions-and-connectors.asciidoc index b35fa57668859..991dfb5ade1d4 100644 --- a/docs/api/actions-and-connectors.asciidoc +++ b/docs/api/actions-and-connectors.asciidoc @@ -28,8 +28,8 @@ include::actions-and-connectors/delete.asciidoc[leveloffset=+1] include::actions-and-connectors/get.asciidoc[leveloffset=+1] include::actions-and-connectors/get_all.asciidoc[leveloffset=+1] include::actions-and-connectors/list.asciidoc[] +include::actions-and-connectors/execute.asciidoc[leveloffset=+1] include::actions-and-connectors/update.asciidoc[leveloffset=+1] -include::actions-and-connectors/execute.asciidoc[] include::actions-and-connectors/legacy/index.asciidoc[] include::actions-and-connectors/legacy/get.asciidoc[] include::actions-and-connectors/legacy/get_all.asciidoc[] diff --git a/docs/api/actions-and-connectors/execute.asciidoc b/docs/api/actions-and-connectors/execute.asciidoc index 2ddd311836281..19b03beaa56fe 100644 --- a/docs/api/actions-and-connectors/execute.asciidoc +++ b/docs/api/actions-and-connectors/execute.asciidoc @@ -1,19 +1,26 @@ [[execute-connector-api]] -=== Execute connector API +== Run connector API ++++ -Execute connector +Run connector ++++ -Executes a connector by ID. +Runs a connector by ID. [[execute-connector-api-request]] -==== {api-request-title} +=== {api-request-title} `POST :/api/actions/connector//_execute` `POST :/s//api/actions/connector//_execute` -[discrete] +[[execute-connector-api-desc]] +=== {api-description-title} + +You can use this API to test an <> that +involves interaction with Kibana services or integrations with third-party +systems. + +[[execute-connector-api-prereq]] === {api-prereq-title} You must have `read` privileges for the *Actions and Connectors* feature in the @@ -24,7 +31,7 @@ If you use an index connector, you must also have `all`, `create`, `index`, or `write` {ref}/security-privileges.html[indices privileges]. [[execute-connector-api-params]] -==== {api-path-parms-title} +=== {api-path-parms-title} `id`:: (Required, string) The ID of the connector. @@ -33,20 +40,20 @@ If you use an index connector, you must also have `all`, `create`, `index`, or (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. [[execute-connector-api-request-body]] -==== {api-request-body-title} +=== {api-request-body-title} `params`:: (Required, object) The parameters of the connector. Parameter properties vary depending on the connector type. For information about the parameter properties, refer to <>. [[execute-connector-api-codes]] -==== {api-response-codes-title} +=== {api-response-codes-title} `200`:: Indicates a successful call. [[execute-connector-api-example]] -==== {api-examples-title} +=== {api-examples-title} Run an index connector: From 019b03f5a0cc13d2f866c0d1e71bbc5ff7462a8e Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 9 Jun 2022 16:57:24 +0200 Subject: [PATCH 37/44] [Synthetics] Renaming label path finder paths (#133996) --- .github/paths-labeller.yml | 7 +++---- x-pack/plugins/synthetics/.buildkite/pipelines/flaky.js | 4 ++-- x-pack/plugins/synthetics/.buildkite/pipelines/flaky.sh | 2 +- x-pack/plugins/synthetics/README.md | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/paths-labeller.yml b/.github/paths-labeller.yml index 37414be6973ad..6800aeea65b9d 100644 --- a/.github/paths-labeller.yml +++ b/.github/paths-labeller.yml @@ -14,10 +14,9 @@ - "x-pack/plugins/fleet/**/*.*" - "x-pack/test/fleet_api_integration/**/*.*" - "Team:uptime": - - "x-pack/plugins/uptime/**/*.*" - - "x-pack/plugins/apm/public/application/csmApp.tsx" - - "x-pack/plugins/apm/public/components/app/RumDashboard/**/*.*" - - "x-pack/plugins/apm/public/components/app/RumDashboard/*.*" + - "x-pack/plugins/synthetics/**/*.*" + - "x-pack/plugins/ux/**/*.*" + - "x-pack/plugins/observability/public/components/shared/exploratory_view/**/*.*" - "x-pack/plugins/apm/server/lib/rum_client/**/*.*" - "x-pack/plugins/apm/server/lib/rum_client/*.*" - "x-pack/plugins/apm/server/routes/rum_client.ts" diff --git a/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.js b/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.js index 6e12f8ca3c921..7a1ee5c582d7d 100644 --- a/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.js @@ -74,8 +74,8 @@ function getGroupRunnerJob(env) { return { command: `${ env[E2E_GREP] ? `GREP="${env[E2E_GREP]}" ` : '' - }.buildkite/scripts/steps/functional/uptime.sh`, - label: `Uptime E2E - Synthetics runner`, + }.buildkite/scripts/steps/functional/synthetics_plugin.sh`, + label: `Synthetics Pluging E2E - Synthetics runner`, agents: { queue: 'n2-4' }, depends_on: BUILD_UUID, parallelism: env[E2E_COUNT], diff --git a/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.sh b/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.sh index 742435f6bec28..eda3407803633 100755 --- a/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.sh +++ b/x-pack/plugins/synthetics/.buildkite/pipelines/flaky.sh @@ -5,4 +5,4 @@ set -euo pipefail UUID="$(cat /proc/sys/kernel/random/uuid)" export UUID -node x-pack/plugins/uptime/.buildkite/pipelines/flaky.js | buildkite-agent pipeline upload +node x-pack/plugins/synthetics/.buildkite/pipelines/flaky.js | buildkite-agent pipeline upload diff --git a/x-pack/plugins/synthetics/README.md b/x-pack/plugins/synthetics/README.md index 34d206a1fdcb8..afb3309e788e9 100644 --- a/x-pack/plugins/synthetics/README.md +++ b/x-pack/plugins/synthetics/README.md @@ -45,7 +45,7 @@ There's also a `rest_api` folder that defines the structure of the RESTful API e Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing ``` -yarn test:jest x-pack/plugins/uptime +yarn test:jest x-pack/plugins/synthetics ``` ### Functional tests From 3aac9b411554daa95d62c733d142a814420429de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 9 Jun 2022 17:08:06 +0200 Subject: [PATCH 38/44] Add a prop to force an open state for the side nav on desktop (#133159) * Add a prop to force an open state for the side nav on desktop * Fixed an issue when a closed state is force open * Added snapshot test for the new prop * TEST: set isForceOpenOnDesktop to true * Revert "TEST: set isForceOpenOnDesktop to true" This reverts commit 68ea903f23ce434b2c97cad15c3ae7e901495124. * Renamed the prop to `canBeCollapsed` and made it work for all screens * Added default value to `with_solution_nav` * Update packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Update packages/kbn-shared-ux-components/src/page_template/with_solution_nav.tsx Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Update packages/kbn-shared-ux-components/src/page_template/with_solution_nav.tsx Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * Added `canBeCollapsed` condition to the collapse button, changed `EuiCollapsibleNavGroup` to use `canBeCollapsed` * Added a panel to fix padding as suggested in the code review * Updated the snapshots * Fixed panel padding and added stories Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> Co-authored-by: cchaos --- .../page_template/page_template.stories.tsx | 14 +- .../__snapshots__/solution_nav.test.tsx.snap | 485 +++++++++++------- .../solution_nav/solution_nav.stories.tsx | 4 + .../solution_nav/solution_nav.test.tsx | 7 + .../solution_nav/solution_nav.tsx | 41 +- .../src/page_template/with_solution_nav.tsx | 5 +- 6 files changed, 357 insertions(+), 199 deletions(-) diff --git a/packages/kbn-shared-ux-components/src/page_template/page_template.stories.tsx b/packages/kbn-shared-ux-components/src/page_template/page_template.stories.tsx index d840e459389b2..6e0cf9fdb2e52 100644 --- a/packages/kbn-shared-ux-components/src/page_template/page_template.stories.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/page_template.stories.tsx @@ -24,7 +24,9 @@ export default { }, }; -type Params = Pick; +type Params = Pick & { + canBeCollapsed: boolean; +}; const noDataConfig = { solution: 'Kibana', @@ -108,7 +110,11 @@ export const PureComponent = (params: Params) => { {content} @@ -128,6 +134,10 @@ PureComponent.argTypes = { control: 'boolean', defaultValue: true, }, + canBeCollapsed: { + control: 'boolean', + defaultValue: true, + }, }; PureComponent.parameters = { diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap b/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap index 62a07d734cf78..1f1ce2036d722 100644 --- a/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap +++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/__snapshots__/solution_nav.test.tsx.snap @@ -7,7 +7,7 @@ exports[`KibanaPageTemplateSolutionNav accepts EuiSideNavProps 1`] = ` className="kbnPageTemplateSolutionNav" initialIsOpen={false} isCollapsible={true} - paddingSize="m" + paddingSize="none" title={ - + + + + + +`; + +exports[`KibanaPageTemplateSolutionNav accepts canBeCollapsed prop 1`] = ` + + +

+ + + +

+
+ } + titleElement="span" + > + + + `; @@ -102,7 +206,7 @@ exports[`KibanaPageTemplateSolutionNav heading accepts more headingProps 1`] = ` className="kbnPageTemplateSolutionNav" initialIsOpen={false} isCollapsible={true} - paddingSize="m" + paddingSize="none" title={ } titleElement="span" - /> + > + + `; @@ -136,7 +245,7 @@ exports[`KibanaPageTemplateSolutionNav renders 1`] = ` className="kbnPageTemplateSolutionNav" initialIsOpen={false} isCollapsible={true} - paddingSize="m" + paddingSize="none" title={ - + + + `; @@ -230,7 +344,7 @@ exports[`KibanaPageTemplateSolutionNav renders with icon 1`] = ` className="kbnPageTemplateSolutionNav" initialIsOpen={false} isCollapsible={true} - paddingSize="m" + paddingSize="none" title={ - + + + `; diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx index 28550a7789a9f..03bf76da8b285 100644 --- a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.stories.tsx @@ -73,6 +73,10 @@ PureComponent.argTypes = { control: 'text', defaultValue: '', }, + canBeCollapsed: { + control: 'boolean', + defaultValue: true, + }, }; PureComponent.parameters = { diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx index 9e2eac4cf20d6..459d186e04d2c 100644 --- a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.test.tsx @@ -100,4 +100,11 @@ describe('KibanaPageTemplateSolutionNav', () => { ); expect(component).toMatchSnapshot(); }); + + test('accepts canBeCollapsed prop', () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); }); diff --git a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx index 191e56db530a9..9ba6438bf94f8 100644 --- a/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/solution_nav/solution_nav.tsx @@ -14,6 +14,7 @@ import { EuiCollapsibleNavGroup, EuiFlyout, EuiFlyoutProps, + EuiPanel, EuiSideNav, EuiSideNavItemType, EuiSideNavProps, @@ -58,6 +59,11 @@ export type KibanaPageTemplateSolutionNavProps = Omit< */ isOpenOnDesktop?: boolean; onCollapse?: () => void; + /** + * Allows hiding of the navigation by the user. + * If false, forces all breakpoint versions into the open state without the ability to hide. + */ + canBeCollapsed?: boolean; }; const FLYOUT_SIZE = 248; @@ -88,6 +94,7 @@ export const KibanaPageTemplateSolutionNav: FunctionComponent< closeFlyoutButtonPosition = 'outside', name, onCollapse, + canBeCollapsed = true, ...rest }) => { const isSmallerBreakpoint = useIsWithinBreakpoints(mobileBreakpoints); @@ -100,7 +107,7 @@ export const KibanaPageTemplateSolutionNav: FunctionComponent< setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile); }; - const isHidden = isLargerBreakpoint && !isOpenOnDesktop; + const isHidden = isLargerBreakpoint && !isOpenOnDesktop && canBeCollapsed; const isCustomSideNav = !!children; const sideNavClasses = classNames('kbnPageTemplateSolutionNav', { @@ -164,21 +171,24 @@ export const KibanaPageTemplateSolutionNav: FunctionComponent< return ( <> {isSmallerBreakpoint && ( + // @ts-expect-error Mismatch in collapsible vs unconllapsible props - {sideNavContent} + + {sideNavContent} + )} {isMediumBreakpoint && ( <> - {isSideNavOpenOnMobile && ( + {(isSideNavOpenOnMobile || !canBeCollapsed) && (
{titleText} @@ -195,10 +206,12 @@ export const KibanaPageTemplateSolutionNav: FunctionComponent<
)} - + {canBeCollapsed && ( + + )} )} {isLargerBreakpoint && ( @@ -208,10 +221,12 @@ export const KibanaPageTemplateSolutionNav: FunctionComponent< {sideNavContent} - + {canBeCollapsed && ( + + )} )} diff --git a/packages/kbn-shared-ux-components/src/page_template/with_solution_nav.tsx b/packages/kbn-shared-ux-components/src/page_template/with_solution_nav.tsx index 07d78dc87f40b..8d804030f9199 100644 --- a/packages/kbn-shared-ux-components/src/page_template/with_solution_nav.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/with_solution_nav.tsx @@ -38,11 +38,14 @@ export const withSolutionNav = (WrappedComponent: ComponentType Date: Thu, 9 Jun 2022 18:10:13 +0300 Subject: [PATCH 39/44] [Lens] Fixes broken drilldowns for gauges and heatmaps (#134008) * [Lens] Fixes broken drilldowns for gauges and heatmaps * Revert drilldown to gauges --- .../lens/public/heatmap_visualization/visualization.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx index 46c86d8c0adb0..afda41dd8f7b8 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx @@ -14,6 +14,7 @@ import { Position } from '@elastic/charts'; import { CUSTOM_PALETTE, PaletteRegistry, CustomPaletteParams } from '@kbn/coloring'; import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { HeatmapIcon } from '@kbn/expression-heatmap-plugin/public'; import type { OperationMetadata, Visualization } from '../types'; import type { HeatmapVisualizationState } from './types'; @@ -154,6 +155,8 @@ export const getHeatmapVisualization = ({ getSuggestions, + triggers: [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + getConfiguration({ state, frame, layerId }) { const datasourceLayer = frame.datasourceLayers[layerId]; From 3df948cbbc7c34ee48126f71ad8f999ed6226655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Thu, 9 Jun 2022 17:18:52 +0200 Subject: [PATCH 40/44] Bump backport to 8.5.0 (#133848) --- package.json | 2 +- scripts/backport.js | 9 ++-- yarn.lock | 109 ++++++++++++++++++++++++++------------------ 3 files changed, 69 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 5ea8bf01eb586..436ffba6171b9 100644 --- a/package.json +++ b/package.json @@ -855,7 +855,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^2.0.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^7.3.1", + "backport": "^8.5.2", "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", diff --git a/scripts/backport.js b/scripts/backport.js index 51ca21e5e2043..dd67bcb44b53f 100644 --- a/scripts/backport.js +++ b/scripts/backport.js @@ -8,9 +8,8 @@ require('../src/setup_node_env/node_version_validator'); var process = require('process'); - -// forward command line args to backport -var args = process.argv.slice(2); - var backport = require('backport'); -backport.backportRun({}, args); + +backport.backportRun({ + processArgs: process.argv.slice(2), // forward command line args to backport +}); diff --git a/yarn.lock b/yarn.lock index 60575ef825057..ce12a30905260 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9066,13 +9066,6 @@ axios@^0.24.0: dependencies: follow-redirects "^1.14.4" -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== - dependencies: - follow-redirects "^1.14.7" - axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -9371,20 +9364,20 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backport@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/backport/-/backport-7.3.1.tgz#c5c57e03c87f5883f769e30efc0e7193ce7670f2" - integrity sha512-F1gjJx/pxn9zI74Np6FlTD8ovqeUbzzgzGyBpoYypAdDTJG8Vxt1jcrcwZtRIaSd7McfXCSoQsinlFzO4qlPcA== +backport@^8.5.2: + version "8.5.2" + resolved "https://registry.yarnpkg.com/backport/-/backport-8.5.2.tgz#500f0f6b7a5bb78996c88553bfb7b6d85a3e627a" + integrity sha512-qtPWz51O02tnRN2qNMhWJvZNaNlh8K+LXNO8ktXiBIEAkrI96VVKzWNvOXcNYl8lOBq0ZYyPkAE6HH8W/l/tdA== dependencies: "@octokit/rest" "^18.12.0" - axios "^0.25.0" + axios "^0.27.2" dedent "^0.7.0" - del "^6.0.0" - dotenv "^16.0.0" + del "^6.1.1" + dotenv "^16.0.1" find-up "^5.0.0" - graphql "^16.3.0" + graphql "^16.5.0" graphql-tag "^2.12.6" - inquirer "^8.2.0" + inquirer "^8.2.3" lodash "^4.17.21" make-dir "^3.1.0" ora "^5.4.1" @@ -9392,9 +9385,9 @@ backport@^7.3.1: strip-json-comments "^3.1.1" terminal-link "^2.1.1" utility-types "^3.10.0" - winston "^3.6.0" - yargs "^17.3.1" - yargs-parser "^21.0.0" + winston "^3.7.2" + yargs "^17.5.1" + yargs-parser "^21.0.1" bail@^1.0.0: version "1.0.2" @@ -12648,6 +12641,20 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" +del@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + delaunator@5: version "5.0.0" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" @@ -13097,10 +13104,10 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" - integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== +dotenv@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== dotenv@^8.0.0, dotenv@^8.1.0: version "8.2.0" @@ -14978,7 +14985,7 @@ follow-redirects@1.12.1: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.14.7, follow-redirects@^1.14.9: +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.14.9: version "1.15.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== @@ -15902,10 +15909,10 @@ graphql-tag@^2.12.6: dependencies: tslib "^2.1.0" -graphql@^16.3.0: - version "16.3.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05" - integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A== +graphql@^16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" + integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== growl@1.10.5: version "1.10.5" @@ -17009,10 +17016,10 @@ inquirer@^7.0.0, inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" - integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== +inquirer@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" + integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== dependencies: ansi-escapes "^4.2.1" chalk "^4.1.1" @@ -17024,10 +17031,11 @@ inquirer@^8.2.0: mute-stream "0.0.8" ora "^5.4.1" run-async "^2.4.0" - rxjs "^7.2.0" + rxjs "^7.5.5" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" + wrap-ansi "^7.0.0" insert-module-globals@^7.0.0: version "7.2.0" @@ -25671,13 +25679,6 @@ rxjs@^7.0.0, rxjs@^7.5.5: dependencies: tslib "^2.1.0" -rxjs@^7.2.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" - integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== - dependencies: - tslib "~2.1.0" - safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -30252,10 +30253,10 @@ winston@^3.0.0, winston@^3.3.3: triple-beam "^1.3.0" winston-transport "^4.4.2" -winston@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.6.0.tgz#be32587a099a292b88c49fac6fa529d478d93fb6" - integrity sha512-9j8T75p+bcN6D00sF/zjFVmPp+t8KMPB1MzbbzYjeN9VWxdsYnTB40TkbNUEXAmILEfChMvAMgidlX64OG3p6w== +winston@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.7.2.tgz#95b4eeddbec902b3db1424932ac634f887c400b1" + integrity sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng== dependencies: "@dabh/diagnostics" "^2.0.2" async "^3.2.3" @@ -30615,6 +30616,11 @@ yargs-parser@^21.0.0: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA== +yargs-parser@^21.0.1: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" @@ -30671,7 +30677,7 @@ yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.0.1, yargs@^17.2.1, yargs@^17.3.1: +yargs@^17.0.1, yargs@^17.2.1: version "17.3.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== @@ -30684,6 +30690,19 @@ yargs@^17.0.1, yargs@^17.2.1, yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yargs@^17.5.1: + version "17.5.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yargs@^3.15.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" From 6beedb6aae26d06da02171448adcfca38f39166b Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Thu, 9 Jun 2022 18:19:26 +0300 Subject: [PATCH 41/44] [Canvas] Fixes Uploaded asset not being saved. (#133166) * Fixed upload assets and update workpad logic. * Fixed onAssetAdd type. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/lib/utils/dataurl.ts | 2 +- .../__stories__/asset_manager.stories.tsx | 11 ++- .../asset_manager/asset_manager.component.tsx | 2 +- .../components/asset_manager/asset_manager.ts | 35 ++++---- .../public/components/function_form/index.tsx | 27 +++++-- .../canvas/public/expression_types/arg.ts | 2 +- .../public/expression_types/function_form.tsx | 2 +- x-pack/plugins/canvas/public/lib/assets.ts | 51 ++++++++++++ .../hooks/use_workpad_persist.test.tsx | 80 ++----------------- .../workpad/hooks/use_workpad_persist.ts | 68 +++------------- .../canvas/public/state/actions/assets.js | 2 +- .../canvas/public/state/reducers/assets.js | 17 ++-- 12 files changed, 125 insertions(+), 174 deletions(-) create mode 100644 x-pack/plugins/canvas/public/lib/assets.ts diff --git a/src/plugins/presentation_util/common/lib/utils/dataurl.ts b/src/plugins/presentation_util/common/lib/utils/dataurl.ts index 9ac232369cdc1..aab816467c3ac 100644 --- a/src/plugins/presentation_util/common/lib/utils/dataurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/dataurl.ts @@ -56,7 +56,7 @@ export function encode(data: any | null, type = 'text/plain') { if (FileReader) { return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result as string); + reader.onload = () => resolve(reader.result as string); reader.onerror = (err) => reject(err); reader.readAsDataURL(data); }); diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/asset_manager.stories.tsx b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/asset_manager.stories.tsx index 5e0e3116e5e4f..9b097de969f5a 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/asset_manager.stories.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/asset_manager.stories.tsx @@ -13,6 +13,13 @@ import { reduxDecorator, getAddonPanelParameters } from '../../../../storybook'; import { AssetManager, AssetManagerComponent } from '..'; import { assets } from './assets'; +const promiseAction = + (actionName: string) => + (...args: any[]): Promise => { + action(actionName)(...args); + return Promise.resolve(); + }; + storiesOf('components/Assets/AssetManager', module) .addDecorator(reduxDecorator({ assets })) .addParameters(getAddonPanelParameters()) @@ -21,13 +28,13 @@ storiesOf('components/Assets/AssetManager', module) )) .add('two assets', () => ( )); diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx index ee1e2d876a45f..af140d5f43d82 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx @@ -71,7 +71,7 @@ export interface Props { assets: AssetType[]; /** Function to invoke when the modal is closed */ onClose: () => void; - onAddAsset: (file: File) => void; + onAddAsset: (file: File) => Promise; } export const AssetManager: FC = (props) => { diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts index 7e37d0db36fdf..e8696ac65c7c0 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts @@ -12,30 +12,39 @@ import { encode } from '@kbn/presentation-util-plugin/public'; // @ts-expect-error untyped local import { findExistingAsset } from '../../lib/find_existing_asset'; import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; -import { getId } from '../../lib/get_id'; +import { createAsset, notifyError } from '../../lib/assets'; import { getAssets } from '../../state/selectors/assets'; // @ts-expect-error untyped local -import { createAsset } from '../../state/actions/assets'; -import { State, AssetType } from '../../../types'; +import { setAsset } from '../../state/actions/assets'; +import { State, AssetType, CanvasWorkpad } from '../../../types'; import { AssetManager as Component } from './asset_manager.component'; +import { getFullWorkpadPersisted } from '../../state/selectors/workpad'; +import { pluginServices } from '../../services'; export const AssetManager = connect( (state: State) => ({ assets: getAssets(state), + workpad: getFullWorkpadPersisted(state), }), (dispatch: Dispatch) => ({ - onAddAsset: (type: string, content: string) => { + onAddAsset: (workpad: CanvasWorkpad, type: AssetType['type'], content: AssetType['value']) => { // make the ID here and pass it into the action - const assetId = getId('asset'); - dispatch(createAsset(type, content, assetId)); + const asset = createAsset(type, content); + const { notify, workpad: workpadService } = pluginServices.getServices(); - // then return the id, so the caller knows the id that will be created - return assetId; + return workpadService + .updateAssets(workpad.id, { ...workpad.assets, [asset.id]: asset }) + .then((res) => { + dispatch(setAsset(asset)); + // then return the id, so the caller knows the id that will be created + return asset.id; + }) + .catch((error) => notifyError(error, notify.error)); }, }), (stateProps, dispatchProps, ownProps) => { - const { assets } = stateProps; + const { assets, workpad } = stateProps; const { onAddAsset } = dispatchProps; // pull values out of assets object @@ -45,10 +54,10 @@ export const AssetManager = connect( return { ...ownProps, assets: assetValues, - onAddAsset: (file: File) => { + onAddAsset: async (file: File) => { const [type, subtype] = get(file, 'type', '').split('/'); if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { - return encode(file).then((dataurl) => { + return await encode(file).then((dataurl) => { const dataurlType = 'dataurl'; const existingId = findExistingAsset(dataurlType, dataurl, assetValues); @@ -56,11 +65,9 @@ export const AssetManager = connect( return existingId; } - return onAddAsset(dataurlType, dataurl); + return onAddAsset(workpad, dataurlType, dataurl); }); } - - return false; }, }; } diff --git a/x-pack/plugins/canvas/public/components/function_form/index.tsx b/x-pack/plugins/canvas/public/components/function_form/index.tsx index 5200fb310c6bf..c40b7d2a90ae7 100644 --- a/x-pack/plugins/canvas/public/components/function_form/index.tsx +++ b/x-pack/plugins/canvas/public/components/function_form/index.tsx @@ -11,9 +11,9 @@ import { Ast } from '@kbn/interpreter'; import deepEqual from 'react-fast-compare'; import { ExpressionAstExpression, ExpressionValue } from '@kbn/expressions-plugin'; import { findExpressionType } from '../../lib/find_expression_type'; -import { getId } from '../../lib/get_id'; + // @ts-expect-error unconverted action function -import { createAsset } from '../../state/actions/assets'; +import { setAsset } from '../../state/actions/assets'; import { fetchContext, setArgument as setArgumentValue, @@ -26,6 +26,7 @@ import { getSelectedPage, getContextForIndex, getGlobalFilterGroups, + getFullWorkpadPersisted, } from '../../state/selectors/workpad'; import { getAssets } from '../../state/selectors/assets'; // @ts-expect-error unconverted lib @@ -33,6 +34,8 @@ import { findExistingAsset } from '../../lib/find_existing_asset'; import { FunctionForm as Component } from './function_form'; import { Args, ArgType, ArgTypeDef } from '../../expression_types/types'; import { State, ExpressionContext, CanvasElement, AssetType } from '../../../types'; +import { useNotifyService, useWorkpadService } from '../../services'; +import { createAsset, notifyError } from '../../lib/assets'; interface FunctionFormProps { name: string; @@ -51,6 +54,9 @@ interface FunctionFormProps { export const FunctionForm: React.FunctionComponent = (props) => { const { expressionIndex, ...restProps } = props; const { nextArgType, path, parentPath, argType } = restProps; + const service = useWorkpadService(); + const notifyService = useNotifyService(); + const dispatch = useDispatch(); const context = useSelector( (state) => getContextForIndex(state, parentPath, expressionIndex), @@ -67,6 +73,8 @@ export const FunctionForm: React.FunctionComponent = (props) shallowEqual ); + const workpad = useSelector((state: State) => getFullWorkpadPersisted(state)); + const addArgument = useCallback( (argName: string, argValue: string | Ast | null) => () => { dispatch(addArgumentValue({ element, pageId, argName, value: argValue, path })); @@ -99,13 +107,18 @@ export const FunctionForm: React.FunctionComponent = (props) const onAssetAddDispatch = useCallback( (type: AssetType['type'], content: AssetType['value']) => { // make the ID here and pass it into the action - const assetId = getId('asset'); - dispatch(createAsset(type, content, assetId)); + const asset = createAsset(type, content); - // then return the id, so the caller knows the id that will be created - return assetId; + return service + .updateAssets(workpad.id, { ...workpad.assets, [asset.id]: asset }) + .then((res) => { + dispatch(setAsset(asset)); + // then return the id, so the caller knows the id that will be created + return asset.id; + }) + .catch((error) => notifyError(error, notifyService.error)); }, - [dispatch] + [dispatch, notifyService, service, workpad.assets, workpad.id] ); const onAssetAdd = useCallback( diff --git a/x-pack/plugins/canvas/public/expression_types/arg.ts b/x-pack/plugins/canvas/public/expression_types/arg.ts index 4ebf470995838..d2a1fc54e828e 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg.ts +++ b/x-pack/plugins/canvas/public/expression_types/arg.ts @@ -76,7 +76,7 @@ export interface DataArg { nextArgType?: ArgType; nextExpressionType?: ExpressionType; onValueAdd: (argName: string, argValue: ArgValue | null) => () => void; - onAssetAdd: (type: AssetType['type'], content: AssetType['value']) => string; + onAssetAdd: (type: AssetType['type'], content: AssetType['value']) => Promise; onValueChange: (value: Ast | string) => void; onValueRemove: () => void; updateContext: (element?: CanvasElement) => void; diff --git a/x-pack/plugins/canvas/public/expression_types/function_form.tsx b/x-pack/plugins/canvas/public/expression_types/function_form.tsx index c5440b19441ce..5acc137df0460 100644 --- a/x-pack/plugins/canvas/public/expression_types/function_form.tsx +++ b/x-pack/plugins/canvas/public/expression_types/function_form.tsx @@ -51,7 +51,7 @@ export type RenderArgData = BaseFormProps & { onValueChange: (argName: string, argIndex: number) => (value: string | Ast) => void; onValueRemove: (argName: string, argIndex: number) => () => void; onContainerRemove: () => void; - onAssetAdd: (type: AssetType['type'], content: AssetType['value']) => string; + onAssetAdd: (type: AssetType['type'], content: AssetType['value']) => Promise; updateContext: (element?: CanvasElement) => void; typeInstance?: ExpressionType; }; diff --git a/x-pack/plugins/canvas/public/lib/assets.ts b/x-pack/plugins/canvas/public/lib/assets.ts new file mode 100644 index 0000000000000..d7fd0ecbac57f --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/assets.ts @@ -0,0 +1,51 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { AssetType, CanvasAsset } from '../../types'; +import { CanvasNotifyService } from '../services/notify'; +import { getId } from './get_id'; + +const strings = { + getSaveFailureTitle: () => + i18n.translate('xpack.canvas.error.esPersist.saveFailureTitle', { + defaultMessage: "Couldn't save your changes to Elasticsearch", + }), + getTooLargeErrorMessage: () => + i18n.translate('xpack.canvas.error.esPersist.tooLargeErrorMessage', { + defaultMessage: + 'The server gave a response that the workpad data was too large. This usually means uploaded image assets that are too large for Kibana or a proxy. Try removing some assets in the asset manager.', + }), + getUpdateFailureTitle: () => + i18n.translate('xpack.canvas.error.esPersist.updateFailureTitle', { + defaultMessage: "Couldn't update workpad", + }), +}; + +export const createAsset = (type: AssetType['type'], content: AssetType['value']): CanvasAsset => ({ + id: getId('asset'), + type, + value: content, + '@created': new Date().toISOString(), +}); + +export const notifyError = (err: any, notifyErrorFn: CanvasNotifyService['error']) => { + const statusCode = err.response && err.response.status; + switch (statusCode) { + case 400: + return notifyErrorFn(err.response, { + title: strings.getSaveFailureTitle(), + }); + case 413: + return notifyErrorFn(strings.getTooLargeErrorMessage(), { + title: strings.getSaveFailureTitle(), + }); + default: + return notifyErrorFn(err, { + title: strings.getUpdateFailureTitle(), + }); + } +}; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx index 3ef93905f7e31..395cf774760fd 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx @@ -11,6 +11,7 @@ const mockGetState = jest.fn(); const mockUpdateWorkpad = jest.fn(); const mockUpdateAssets = jest.fn(); const mockUpdate = jest.fn(); + const mockNotifyError = jest.fn(); // Mock the hooks and actions used by the UseWorkpad hook @@ -34,6 +35,10 @@ describe('useWorkpadPersist', () => { jest.resetAllMocks(); }); + afterAll(() => { + jest.clearAllMocks(); + }); + test('initial render does not persist state', () => { const state = { persistent: { @@ -50,8 +55,6 @@ describe('useWorkpadPersist', () => { renderHook(useWorkpadPersist); expect(mockUpdateWorkpad).not.toBeCalled(); - expect(mockUpdateAssets).not.toBeCalled(); - expect(mockUpdate).not.toBeCalled(); }); test('changes to workpad cause a workpad update', () => { @@ -82,73 +85,11 @@ describe('useWorkpadPersist', () => { expect(mockUpdateWorkpad).toHaveBeenCalled(); }); - test('changes to assets cause an asset update', () => { - const state = { - persistent: { - workpad: { some: 'workpad' }, - }, - assets: { - asset1: 'some asset', - asset2: 'other asset', - }, - }; - - mockGetState.mockReturnValue(state); - - const { rerender } = renderHook(useWorkpadPersist); - - const newState = { - ...state, - assets: { - asset1: 'some asset', - }, - }; - mockGetState.mockReturnValue(newState); - - rerender(); - - expect(mockUpdateAssets).toHaveBeenCalled(); - }); - - test('changes to both assets and workpad causes a full update', () => { - const state = { - persistent: { - workpad: { some: 'workpad' }, - }, - assets: { - asset1: 'some asset', - asset2: 'other asset', - }, - }; - - mockGetState.mockReturnValue(state); - - const { rerender } = renderHook(useWorkpadPersist); - - const newState = { - persistent: { - workpad: { new: 'workpad' }, - }, - assets: { - asset1: 'some asset', - }, - }; - mockGetState.mockReturnValue(newState); - - rerender(); - - expect(mockUpdate).toHaveBeenCalled(); - }); - test('non changes causes no updated', () => { const state = { persistent: { workpad: { some: 'workpad' }, }, - assets: { - asset1: 'some asset', - asset2: 'other asset', - }, }; mockGetState.mockReturnValue(state); @@ -156,9 +97,7 @@ describe('useWorkpadPersist', () => { rerender(); - expect(mockUpdate).not.toHaveBeenCalled(); expect(mockUpdateWorkpad).not.toHaveBeenCalled(); - expect(mockUpdateAssets).not.toHaveBeenCalled(); }); test('non write permissions causes no updates', () => { @@ -166,10 +105,6 @@ describe('useWorkpadPersist', () => { persistent: { workpad: { some: 'workpad' }, }, - assets: { - asset1: 'some asset', - asset2: 'other asset', - }, transient: { canUserWrite: false, }, @@ -182,9 +117,6 @@ describe('useWorkpadPersist', () => { persistent: { workpad: { new: 'workpad value' }, }, - assets: { - asset3: 'something', - }, transient: { canUserWrite: false, }, @@ -193,8 +125,6 @@ describe('useWorkpadPersist', () => { rerender(); - expect(mockUpdate).not.toHaveBeenCalled(); expect(mockUpdateWorkpad).not.toHaveBeenCalled(); - expect(mockUpdateAssets).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts index 62c83e0411848..45e21e59717ad 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts @@ -4,86 +4,36 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useEffect, useCallback } from 'react'; -import { isEqual } from 'lodash'; +import { useEffect } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import { useSelector } from 'react-redux'; -import { i18n } from '@kbn/i18n'; import { CanvasWorkpad, State } from '../../../../types'; -import { getWorkpad, getFullWorkpadPersisted } from '../../../state/selectors/workpad'; +import { getWorkpad } from '../../../state/selectors/workpad'; import { canUserWrite } from '../../../state/selectors/app'; -import { getAssetIds } from '../../../state/selectors/assets'; import { useWorkpadService, useNotifyService } from '../../../services'; - -const strings = { - getSaveFailureTitle: () => - i18n.translate('xpack.canvas.error.esPersist.saveFailureTitle', { - defaultMessage: "Couldn't save your changes to Elasticsearch", - }), - getTooLargeErrorMessage: () => - i18n.translate('xpack.canvas.error.esPersist.tooLargeErrorMessage', { - defaultMessage: - 'The server gave a response that the workpad data was too large. This usually means uploaded image assets that are too large for Kibana or a proxy. Try removing some assets in the asset manager.', - }), - getUpdateFailureTitle: () => - i18n.translate('xpack.canvas.error.esPersist.updateFailureTitle', { - defaultMessage: "Couldn't update workpad", - }), -}; +import { notifyError } from '../../../lib/assets'; export const useWorkpadPersist = () => { const service = useWorkpadService(); const notifyService = useNotifyService(); - const notifyError = useCallback( - (err: any) => { - const statusCode = err.response && err.response.status; - switch (statusCode) { - case 400: - return notifyService.error(err.response, { - title: strings.getSaveFailureTitle(), - }); - case 413: - return notifyService.error(strings.getTooLargeErrorMessage(), { - title: strings.getSaveFailureTitle(), - }); - default: - return notifyService.error(err, { - title: strings.getUpdateFailureTitle(), - }); - } - }, - [notifyService] - ); - // Watch for workpad state or workpad assets to change and then persist those changes - const [workpad, assetIds, fullWorkpad, canWrite]: [ - CanvasWorkpad, - Array, - CanvasWorkpad, - boolean - ] = useSelector((state: State) => [ + // Watch for workpad state and then persist those changes + const [workpad, canWrite]: [CanvasWorkpad, boolean] = useSelector((state: State) => [ getWorkpad(state), - getAssetIds(state), - getFullWorkpadPersisted(state), canUserWrite(state), ]); const previousWorkpad = usePrevious(workpad); - const previousAssetIds = usePrevious(assetIds); const workpadChanged = previousWorkpad && workpad !== previousWorkpad; - const assetsChanged = previousAssetIds && !isEqual(assetIds, previousAssetIds); useEffect(() => { if (canWrite) { - if (workpadChanged && assetsChanged) { - service.update(workpad.id, fullWorkpad).catch(notifyError); - } if (workpadChanged) { - service.updateWorkpad(workpad.id, workpad).catch(notifyError); - } else if (assetsChanged) { - service.updateAssets(workpad.id, fullWorkpad.assets).catch(notifyError); + service.updateWorkpad(workpad.id, workpad).catch((err) => { + notifyError(err, notifyService.error); + }); } } - }, [service, workpad, fullWorkpad, workpadChanged, assetsChanged, canWrite, notifyError]); + }, [service, workpad, workpadChanged, canWrite, notifyService.error]); }; diff --git a/x-pack/plugins/canvas/public/state/actions/assets.js b/x-pack/plugins/canvas/public/state/actions/assets.js index ab3083913cce5..5db4204d458c5 100644 --- a/x-pack/plugins/canvas/public/state/actions/assets.js +++ b/x-pack/plugins/canvas/public/state/actions/assets.js @@ -7,8 +7,8 @@ import { createAction } from 'redux-actions'; -export const createAsset = createAction('createAsset', (type, value, id) => ({ type, value, id })); export const setAssetValue = createAction('setAssetContent', (id, value) => ({ id, value })); +export const setAsset = createAction('setAsset', (asset) => ({ asset })); export const removeAsset = createAction('removeAsset'); export const setAssets = createAction('setAssets'); export const resetAssets = createAction('resetAssets'); diff --git a/x-pack/plugins/canvas/public/state/reducers/assets.js b/x-pack/plugins/canvas/public/state/reducers/assets.js index 0b5fdaa06fa78..c42d832b7e6a7 100644 --- a/x-pack/plugins/canvas/public/state/reducers/assets.js +++ b/x-pack/plugins/canvas/public/state/reducers/assets.js @@ -8,22 +8,12 @@ import { handleActions, combineActions } from 'redux-actions'; import immutable from 'object-path-immutable'; import { get } from 'lodash'; -import { createAsset, setAssetValue, removeAsset, setAssets, resetAssets } from '../actions/assets'; -import { getId } from '../../lib/get_id'; +import { setAssetValue, removeAsset, setAssets, resetAssets, setAsset } from '../actions/assets'; const { set, assign, del } = immutable; export const assetsReducer = handleActions( { - [createAsset]: (assetState, { payload }) => { - const asset = { - id: getId('asset'), - '@created': new Date().toISOString(), - ...payload, - }; - return set(assetState, asset.id, asset); - }, - [setAssetValue]: (assetState, { payload }) => { const { id, value } = payload; const asset = get(assetState, [id]); @@ -32,7 +22,10 @@ export const assetsReducer = handleActions( } return assign(assetState, id, { value }); }, - + [setAsset]: (assetState, { payload }) => { + const { asset } = payload; + return set(assetState, asset.id, asset); + }, [removeAsset]: (assetState, { payload: assetId }) => { return del(assetState, assetId); }, From e420c1dc3510a69840dae9a5dd3a4768b4ecb194 Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Thu, 9 Jun 2022 16:48:40 +0100 Subject: [PATCH 42/44] Don't send timeline telemetry record if alert timeline is empty. (#134023) --- .../server/lib/telemetry/__mocks__/index.ts | 9 +++++-- .../lib/telemetry/__mocks__/timeline.ts | 2 +- .../lib/telemetry/tasks/timelines.test.ts | 24 +++++++++++++++++ .../server/lib/telemetry/tasks/timelines.ts | 26 +++++++++++-------- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts index d91f08f15cedf..2cd95f0e58951 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts @@ -64,8 +64,13 @@ const stubLicenseInfo = { }; export const createMockTelemetryReceiver = ( - diagnosticsAlert?: unknown + diagnosticsAlert?: unknown, + emptyTimelineTree?: boolean ): jest.Mocked => { + const processTreeResponse = emptyTimelineTree + ? Promise.resolve([]) + : Promise.resolve(Promise.resolve(stubProcessTree())); + return { start: jest.fn(), fetchClusterInfo: jest.fn().mockReturnValue(stubClusterInfo), @@ -82,7 +87,7 @@ export const createMockTelemetryReceiver = ( fetchTimelineEndpointAlerts: jest .fn() .mockReturnValue(Promise.resolve(stubEndpointAlertResponse())), - buildProcessTree: jest.fn().mockReturnValue(Promise.resolve(stubProcessTree())), + buildProcessTree: jest.fn().mockReturnValue(processTreeResponse), fetchTimelineEvents: jest.fn().mockReturnValue(Promise.resolve(stubFetchTimelineEvents())), } as unknown as jest.Mocked; }; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts index 2cb1fddaa47cc..7b3d124e2508f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/timeline.ts @@ -20,7 +20,7 @@ export const stubEndpointAlertResponse = () => { }, hits: { total: { - value: 47, + value: 1, relation: 'eq', }, max_score: 0, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts index 36f6d3caa6d46..0b69ecf9e3639 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.test.ts @@ -36,5 +36,29 @@ describe('timeline telemetry task test', () => { expect(mockTelemetryReceiver.buildProcessTree).toHaveBeenCalled(); expect(mockTelemetryReceiver.fetchTimelineEvents).toHaveBeenCalled(); expect(mockTelemetryReceiver.fetchTimelineEndpointAlerts).toHaveBeenCalled(); + expect(mockTelemetryEventsSender.sendOnDemand).toHaveBeenCalled(); + }); + + test('if no timeline events received it should not send a telemetry record', async () => { + const testTaskExecutionPeriod = { + last: undefined, + current: new Date().toISOString(), + }; + const mockTelemetryEventsSender = createMockTelemetryEventsSender(); + const mockTelemetryReceiver = createMockTelemetryReceiver(null, true); + const telemetryTelemetryTaskConfig = createTelemetryTimelineTaskConfig(); + + await telemetryTelemetryTaskConfig.runTask( + 'test-timeline-task-id', + logger, + mockTelemetryReceiver, + mockTelemetryEventsSender, + testTaskExecutionPeriod + ); + + expect(mockTelemetryReceiver.buildProcessTree).toHaveBeenCalled(); + expect(mockTelemetryReceiver.fetchTimelineEvents).toHaveBeenCalled(); + expect(mockTelemetryReceiver.fetchTimelineEndpointAlerts).toHaveBeenCalled(); + expect(mockTelemetryEventsSender.sendOnDemand).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts index c85d933172786..be98de355a82c 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts @@ -130,19 +130,23 @@ export function createTelemetryTimelineTaskConfig() { telemetryTimeline.push(timelineTelemetryEvent); } - const record: TimelineTelemetryTemplate = { - '@timestamp': moment().toISOString(), - ...baseDocument, - alert_id: alertUUID, - event_id: eventId, - timeline: telemetryTimeline, - }; - - sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [record]); - counter += 1; + if (telemetryTimeline.length >= 1) { + const record: TimelineTelemetryTemplate = { + '@timestamp': moment().toISOString(), + ...baseDocument, + alert_id: alertUUID, + event_id: eventId, + timeline: telemetryTimeline, + }; + + sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [record]); + counter += 1; + } else { + logger.debug('no events in timeline'); + } } - logger.debug(`sent ${counter} timelines. exiting telemetry task.`); + logger.debug(`sent ${counter} timelines. concluding timeline task.`); return counter; }, }; From fa4d1020fb87ea3da4d84d90b4ccf660c4041f94 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 9 Jun 2022 11:44:39 -0500 Subject: [PATCH 43/44] [artifacts] Remove docker cloud context verification (#134046) This test was added as a verification step for daily releasable artifacts. We had originally planned on the release manager consuming this context. The expected workflow has changed now to us building and publishing the docker image instead, which already has coverage in the Build and Deploy to Cloud step. --- .buildkite/pipelines/artifacts.yml | 12 ------------ .buildkite/scripts/steps/artifacts/docker_context.sh | 6 ------ 2 files changed, 18 deletions(-) diff --git a/.buildkite/pipelines/artifacts.yml b/.buildkite/pipelines/artifacts.yml index 549ee77bd759d..bfe5fe190ea16 100644 --- a/.buildkite/pipelines/artifacts.yml +++ b/.buildkite/pipelines/artifacts.yml @@ -51,18 +51,6 @@ steps: - exit_status: '*' limit: 1 - - command: KIBANA_DOCKER_CONTEXT=cloud .buildkite/scripts/steps/artifacts/docker_context.sh - label: 'Docker Context Verification' - soft_fail: true - agents: - queue: n2-2 - timeout_in_minutes: 30 - if: "build.env('RELEASE_BUILD') == null || build.env('RELEASE_BUILD') == '' || build.env('RELEASE_BUILD') == 'false'" - retry: - automatic: - - exit_status: '*' - limit: 1 - - command: KIBANA_DOCKER_CONTEXT=ubi .buildkite/scripts/steps/artifacts/docker_context.sh label: 'Docker Context Verification' agents: diff --git a/.buildkite/scripts/steps/artifacts/docker_context.sh b/.buildkite/scripts/steps/artifacts/docker_context.sh index 8076ebd043545..d7bbe2ecb27cf 100755 --- a/.buildkite/scripts/steps/artifacts/docker_context.sh +++ b/.buildkite/scripts/steps/artifacts/docker_context.sh @@ -26,11 +26,5 @@ fi tar -xf "target/$DOCKER_CONTEXT_FILE" -C "$DOCKER_BUILD_FOLDER" cd $DOCKER_BUILD_FOLDER -buildkite-agent artifact download "kibana-$FULL_VERSION-linux-x86_64.tar.gz" . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" -if [[ "$KIBANA_DOCKER_CONTEXT" == "cloud" ]]; then - buildkite-agent artifact download "metricbeat-$FULL_VERSION-linux-x86_64.tar.gz" . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" - buildkite-agent artifact download "filebeat-$FULL_VERSION-linux-x86_64.tar.gz" . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" -fi - echo "--- Build context" docker build . From 3cd5257369e7637dbc8c35e0b635d4dc8eaf0944 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Thu, 9 Jun 2022 12:47:23 -0400 Subject: [PATCH 44/44] [RAM] add Alert fly-out from security solution (#133691) * add security solution flyout * fix types * performance on browserfield * cleanup * miss to rmv something * fix types + unit test * fix tets * review Jiawei * review II + cast a type * eui review * formatting Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../alerts_flyout/alerts_flyout_body.tsx | 2 +- .../common/search_strategy/index.ts | 7 +- .../register_alerts_table_configuration.tsx | 16 +- .../render_cell_value.tsx | 62 +++++ .../flyout/back_to_alert_details_link.tsx | 39 +++ .../side_panel/event_details/flyout/body.tsx | 110 ++++++++ .../{ => flyout}/footer.test.tsx | 38 +-- .../event_details/{ => flyout}/footer.tsx | 42 +-- .../event_details/flyout/header.tsx | 76 ++++++ .../side_panel/event_details/flyout/index.tsx | 188 +++++++++++++ .../side_panel/event_details/helpers.tsx | 53 ++++ .../side_panel/event_details/index.tsx | 246 +++++------------- .../use_host_isolation_tools.tsx | 108 ++++++++ .../alerts_flyout/alerts_flyout.test.tsx | 56 +++- .../alerts_flyout/alerts_flyout.tsx | 97 +++++-- .../register_alerts_table_configuration.tsx | 11 +- .../sections/alerts_table/alerts_table.scss | 3 + .../sections/alerts_table/alerts_table.tsx | 39 +-- .../alerts_table/alerts_table_state.test.tsx | 6 +- .../hooks/use_fetch_alerts.test.tsx | 4 + .../alerts_table/hooks/use_fetch_alerts.tsx | 7 +- .../triggers_actions_ui/public/types.ts | 2 +- 22 files changed, 936 insertions(+), 276 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx rename x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/{ => flyout}/footer.test.tsx (68%) rename x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/{ => flyout}/footer.tsx (80%) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx create mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout_body.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout_body.tsx index b6964381bbb3d..f747aeb4c5eb8 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout_body.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_flyout/alerts_flyout_body.tsx @@ -36,7 +36,7 @@ export default function AlertsFlyoutBody(props: FlyoutProps) { const { observabilityRuleTypeRegistry } = usePluginContext(); const alert = props.alert.start ? props.alert - : parseAlert(observabilityRuleTypeRegistry)(props.alert); + : parseAlert(observabilityRuleTypeRegistry)(props.alert as unknown as Record); const { services } = useKibana(); const { http } = services; const dateFormat = useUiSetting('dateFormat'); diff --git a/x-pack/plugins/rule_registry/common/search_strategy/index.ts b/x-pack/plugins/rule_registry/common/search_strategy/index.ts index 90ea6fbf95e70..4e0f3c63f3535 100644 --- a/x-pack/plugins/rule_registry/common/search_strategy/index.ts +++ b/x-pack/plugins/rule_registry/common/search_strategy/index.ts @@ -66,7 +66,12 @@ type DotNestedKeys = [D] extends [never] : never; export type EcsFields = DotNestedKeys>; + +export interface BasicFields { + _id: string; + _index: string; +} export type EcsFieldsResponse = { [Property in EcsFields]: string[]; -}; +} & BasicFields; export type RuleRegistrySearchResponse = IEsSearchResponse; diff --git a/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx b/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx index 3a1bcee1eed51..3117090a2fe6c 100644 --- a/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/triggers_actions_ui/register_alerts_table_configuration.tsx @@ -6,12 +6,17 @@ */ import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { AlertsTableConfigurationRegistryContract } from '@kbn/triggers-actions-ui-plugin/public'; +import type { + AlertsTableConfigurationRegistryContract, + GetRenderCellValue, +} from '@kbn/triggers-actions-ui-plugin/public'; import { APP_ID } from '../../../../common/constants'; import { getTimelinesInStorageByIds } from '../../../timelines/containers/local_storage'; import { TimelineId } from '../../../../common/types'; import { columns } from '../../../detections/configurations/security_solution_detections'; +import { useRenderCellValue } from '../../../detections/configurations/security_solution_detections/render_cell_value'; +import { useToGetInternalFlyout } from '../../../timelines/components/side_panel/event_details/flyout'; const registerAlertsTableConfiguration = ( registry: AlertsTableConfigurationRegistryContract, @@ -21,10 +26,17 @@ const registerAlertsTableConfiguration = ( return; } const timelineStorage = getTimelinesInStorageByIds(storage, [TimelineId.detectionsPage]); - const alertColumns = timelineStorage?.[TimelineId.detectionsPage]?.columns ?? columns; + const columnsFormStorage = timelineStorage?.[TimelineId.detectionsPage]?.columns ?? []; + const alertColumns = columnsFormStorage.length ? columnsFormStorage : columns; + registry.register({ id: APP_ID, columns: alertColumns, + getRenderCellValue: useRenderCellValue as GetRenderCellValue, + useInternalFlyout: () => { + const { header, body, footer } = useToGetInternalFlyout(); + return { header, body, footer }; + }, }); }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx index 4454653f6bc5c..09dcc9970ac20 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx @@ -7,6 +7,9 @@ import { EuiDataGridCellValueElementProps } from '@elastic/eui'; import React from 'react'; +import { TimelineId } from '../../../../common/types'; +import { useSourcererDataView } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering'; import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; @@ -59,3 +62,62 @@ export const RenderCellValue: React.FC< truncate={truncate} /> ); + +export const useRenderCellValue = ({ + setFlyoutAlert, +}: { + setFlyoutAlert?: (data: never) => void; +}) => { + const { browserFields } = useSourcererDataView(SourcererScopeName.detections); + return ({ + columnId, + colIndex, + data, + ecsData, + eventId, + globalFilters, + header, + isDetails = false, + isDraggable = false, + isExpandable, + isExpanded, + linkValues, + rowIndex, + rowRenderers, + setCellProps, + truncate = true, + }: CellValueElementProps) => { + const splitColumnId = columnId.split('.'); + let myHeader = header ?? { id: columnId }; + if (splitColumnId.length > 1 && browserFields[splitColumnId[0]]) { + const attr = (browserFields[splitColumnId[0]].fields ?? {})[columnId] ?? {}; + myHeader = { ...myHeader, ...attr }; + } else if (splitColumnId.length === 1) { + const attr = (browserFields.base.fields ?? {})[columnId] ?? {}; + myHeader = { ...myHeader, ...attr }; + } + + return ( + + ); + }; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx new file mode 100644 index 0000000000000..2ce97cfbce32e --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx @@ -0,0 +1,39 @@ +/* + * 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 { EuiButtonEmpty, EuiText, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { + ISOLATE_HOST, + UNISOLATE_HOST, +} from '../../../../../detections/components/host_isolation/translations'; +import { ALERT_DETAILS } from '../translations'; + +const BackToAlertDetailsLinkComponent = ({ + showAlertDetails, + isolateAction, +}: { + showAlertDetails: () => void; + isolateAction: 'isolateHost' | 'unisolateHost'; +}) => { + return ( + <> + + +

{ALERT_DETAILS}

+
+
+ +

{isolateAction === 'isolateHost' ? ISOLATE_HOST : UNISOLATE_HOST}

+
+ + ); +}; + +const BackToAlertDetailsLink = React.memo(BackToAlertDetailsLinkComponent); + +export { BackToAlertDetailsLink }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx new file mode 100644 index 0000000000000..4244d33859320 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx @@ -0,0 +1,110 @@ +/* + * 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 { EuiFlyoutBody } from '@elastic/eui'; +import styled from 'styled-components'; +import React from 'react'; +import { EndpointIsolateSuccess } from '../../../../../common/components/endpoint/host_isolation'; +import { HostIsolationPanel } from '../../../../../detections/components/host_isolation'; +import { BrowserFields, TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; +import { ExpandableEvent, HandleOnEventClosed } from '../expandable_event'; +import { HostRisk } from '../../../../../risk_score/containers'; + +const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` + .euiFlyoutBody__overflow { + display: flex; + flex: 1; + overflow: hidden; + + .euiFlyoutBody__overflowContent { + flex: 1; + overflow: hidden; + padding: ${({ theme }) => `0 ${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.m}`}; + } + } +`; + +interface FlyoutBodyComponentProps { + alertId: string; + browserFields: BrowserFields; + detailsData: TimelineEventsDetailsItem[] | null; + event: { eventId: string; indexName: string }; + handleIsolationActionSuccess: () => void; + handleOnEventClosed: HandleOnEventClosed; + hostName: string; + hostRisk: HostRisk | null; + isAlert: boolean; + isDraggable?: boolean; + isReadOnly?: boolean; + isolateAction: 'isolateHost' | 'unisolateHost'; + isIsolateActionSuccessBannerVisible: boolean; + isHostIsolationPanelOpen: boolean; + loading: boolean; + rawEventData: object | undefined; + showAlertDetails: () => void; + timelineId: string; +} + +const FlyoutBodyComponent = ({ + alertId, + browserFields, + detailsData, + event, + handleIsolationActionSuccess, + handleOnEventClosed, + hostName, + hostRisk, + isAlert, + isDraggable, + isReadOnly, + isolateAction, + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + loading, + rawEventData, + showAlertDetails, + timelineId, +}: FlyoutBodyComponentProps) => { + return ( + + {isIsolateActionSuccessBannerVisible && ( + + )} + {isHostIsolationPanelOpen ? ( + + ) : ( + + )} + + ); +}; + +const FlyoutBody = React.memo(FlyoutBodyComponent); + +export { FlyoutBody }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx similarity index 68% rename from x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.test.tsx rename to x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx index 55cdcb229bba4..973875991da5f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx @@ -6,14 +6,14 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import { EventDetailsFooter } from './footer'; -import '../../../../common/mock/match_media'; -import { TestProviders } from '../../../../common/mock'; -import { TimelineId } from '../../../../../common/types/timeline'; -import { Ecs } from '../../../../../common/ecs'; -import { mockAlertDetailsData } from '../../../../common/components/event_details/__mocks__'; -import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; -import { KibanaServices, useKibana } from '../../../../common/lib/kibana'; +import { FlyoutFooter } from './footer'; +import '../../../../../common/mock/match_media'; +import { TestProviders } from '../../../../../common/mock'; +import { TimelineId } from '../../../../../../common/types/timeline'; +import { Ecs } from '../../../../../../common/ecs'; +import { mockAlertDetailsData } from '../../../../../common/components/event_details/__mocks__'; +import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; +import { KibanaServices, useKibana } from '../../../../../common/lib/kibana'; import { coreMock } from '@kbn/core/public/mocks'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; @@ -38,14 +38,14 @@ const mockAlertDetailsDataWithIsObject = mockAlertDetailsData.map((detail) => { }; }) as TimelineEventsDetailsItem[]; -jest.mock('../../../../../common/endpoint/service/host_isolation/utils', () => { +jest.mock('../../../../../../common/endpoint/service/host_isolation/utils', () => { return { isIsolationSupported: jest.fn().mockReturnValue(true), }; }); jest.mock( - '../../../../detections/containers/detection_engine/alerts/use_host_isolation_status', + '../../../../../detections/containers/detection_engine/alerts/use_host_isolation_status', () => { return { useHostIsolationStatus: jest.fn().mockReturnValue({ @@ -57,30 +57,30 @@ jest.mock( } ); -jest.mock('../../../../common/hooks/use_experimental_features', () => ({ +jest.mock('../../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), })); -jest.mock('../../../../detections/components/user_info', () => ({ +jest.mock('../../../../../detections/components/user_info', () => ({ useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), })); -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../../common/lib/kibana'); jest.mock( - '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges', + '../../../../../detections/containers/detection_engine/alerts/use_alerts_privileges', () => ({ useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }), }) ); -jest.mock('../../../../cases/components/use_insert_timeline'); +jest.mock('../../../../../cases/components/use_insert_timeline'); -jest.mock('../../../../common/utils/endpoint_alert_check', () => { +jest.mock('../../../../../common/utils/endpoint_alert_check', () => { return { isAlertFromEndpointAlert: jest.fn().mockReturnValue(true), isAlertFromEndpointEvent: jest.fn().mockReturnValue(true), }; }); jest.mock( - '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline', + '../../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline', () => { return { useInvestigateInTimeline: jest.fn().mockReturnValue({ @@ -90,7 +90,7 @@ jest.mock( }; } ); -jest.mock('../../../../detections/components/alerts_table/actions'); +jest.mock('../../../../../detections/components/alerts_table/actions'); const defaultProps = { timelineId: TimelineId.test, @@ -126,7 +126,7 @@ describe('event details footer component', () => { test('it renders the take action dropdown', () => { const wrapper = render( - + ); expect(wrapper.getByTestId('take-action-dropdown-btn')).toBeTruthy(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx similarity index 80% rename from x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx rename to x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx index 86a8047b3ad76..6a17592f5b764 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx @@ -9,20 +9,20 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { find } from 'lodash/fp'; import { connect, ConnectedProps } from 'react-redux'; -import { TakeActionDropdown } from '../../../../detections/components/take_action_dropdown'; -import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; -import { TimelineId } from '../../../../../common/types'; -import { useExceptionFlyout } from '../../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout'; -import { AddExceptionFlyoutWrapper } from '../../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; -import { EventFiltersFlyout } from '../../../../management/pages/event_filters/view/components/event_filters_flyout'; -import { useEventFilterModal } from '../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; -import { getFieldValue } from '../../../../detections/components/host_isolation/helpers'; -import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; -import { Ecs } from '../../../../../common/ecs'; -import { inputsModel, inputsSelectors, State } from '../../../../common/store'; -import { OsqueryFlyout } from '../../../../detections/components/osquery/osquery_flyout'; - -interface EventDetailsFooterProps { +import { TakeActionDropdown } from '../../../../../detections/components/take_action_dropdown'; +import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; +import { useExceptionFlyout } from '../../../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout'; +import { AddExceptionFlyoutWrapper } from '../../../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; +import { EventFiltersFlyout } from '../../../../../management/pages/event_filters/view/components/event_filters_flyout'; +import { useEventFilterModal } from '../../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; +import { getFieldValue } from '../../../../../detections/components/host_isolation/helpers'; +import { Status } from '../../../../../../common/detection_engine/schemas/common/schemas'; +import { Ecs } from '../../../../../../common/ecs'; +import { inputsModel, inputsSelectors, State } from '../../../../../common/store'; +import { OsqueryFlyout } from '../../../../../detections/components/osquery/osquery_flyout'; +import { TimelineId } from '../../../../../../common/types'; + +interface FlyoutFooterProps { detailsData: TimelineEventsDetailsItem[] | null; detailsEcsData: Ecs | null; expandedEvent: { @@ -32,6 +32,7 @@ interface EventDetailsFooterProps { }; handleOnEventClosed: () => void; isHostIsolationPanelOpen: boolean; + isReadOnly?: boolean; loadingEventDetails: boolean; onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void; timelineId: string; @@ -45,20 +46,21 @@ interface AddExceptionModalWrapperData { ruleName: string; } -export const EventDetailsFooterComponent = React.memo( +export const FlyoutFooterComponent = React.memo( ({ detailsData, detailsEcsData, expandedEvent, handleOnEventClosed, isHostIsolationPanelOpen, + isReadOnly, loadingEventDetails, onAddIsolationStatusClick, timelineId, globalQuery, timelineQuery, refetchFlyoutData, - }: EventDetailsFooterProps & PropsFromRedux) => { + }: FlyoutFooterProps & PropsFromRedux) => { const ruleIndex = useMemo( () => find({ category: 'signal', field: 'signal.rule.index' }, detailsData)?.values ?? @@ -118,6 +120,10 @@ export const EventDetailsFooterComponent = React.memo( setOsqueryFlyoutOpenWithAgentId(null); }, [setOsqueryFlyoutOpenWithAgentId]); + if (isReadOnly) { + return null; + } + return ( <> @@ -175,7 +181,7 @@ export const EventDetailsFooterComponent = React.memo( const makeMapStateToProps = () => { const getGlobalQueries = inputsSelectors.globalQuery(); const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); - const mapStateToProps = (state: State, { timelineId }: EventDetailsFooterProps) => { + const mapStateToProps = (state: State, { timelineId }: FlyoutFooterProps) => { return { globalQuery: getGlobalQueries(state), timelineQuery: getTimelineQuery(state, timelineId), @@ -188,4 +194,4 @@ const connector = connect(makeMapStateToProps); type PropsFromRedux = ConnectedProps; -export const EventDetailsFooter = connector(React.memo(EventDetailsFooterComponent)); +export const FlyoutFooter = connector(React.memo(FlyoutFooterComponent)); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx new file mode 100644 index 0000000000000..39a4899dbc33c --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx @@ -0,0 +1,76 @@ +/* + * 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 { EuiFlyoutHeader } from '@elastic/eui'; +import React from 'react'; + +import { ExpandableEventTitle } from '../expandable_event'; +import { BackToAlertDetailsLink } from './back_to_alert_details_link'; + +interface FlyoutHeaderComponentProps { + isAlert: boolean; + isHostIsolationPanelOpen: boolean; + isolateAction: 'isolateHost' | 'unisolateHost'; + loading: boolean; + ruleName: string; + showAlertDetails: () => void; + timestamp: string; +} + +const FlyoutHeaderContentComponent = ({ + isAlert, + isHostIsolationPanelOpen, + isolateAction, + loading, + ruleName, + showAlertDetails, + timestamp, +}: FlyoutHeaderComponentProps) => { + return ( + <> + {isHostIsolationPanelOpen ? ( + + ) : ( + + )} + + ); +}; +const FlyoutHeaderContent = React.memo(FlyoutHeaderContentComponent); + +const FlyoutHeaderComponent = ({ + isAlert, + isHostIsolationPanelOpen, + isolateAction, + loading, + ruleName, + showAlertDetails, + timestamp, +}: FlyoutHeaderComponentProps) => { + return ( + + + + ); +}; + +const FlyoutHeader = React.memo(FlyoutHeaderComponent); + +export { FlyoutHeader, FlyoutHeaderContent }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx new file mode 100644 index 0000000000000..055da32e74673 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx @@ -0,0 +1,188 @@ +/* + * 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 type { AlertsTableFlyoutBaseProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { EntityType, TimelineId } from '@kbn/timelines-plugin/common'; +import { noop } from 'lodash/fp'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { buildHostNamesFilter } from '../../../../../../common/search_strategy'; +import { HostRisk, useHostRiskScore } from '../../../../../risk_score/containers'; +import { useHostIsolationTools } from '../use_host_isolation_tools'; +import { FlyoutHeaderContent } from './header'; +import { FlyoutBody } from './body'; +import { FlyoutFooter } from './footer'; +import { useTimelineEventsDetails } from '../../../../containers/details'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; +import { useBasicDataFromDetailsData } from '../helpers'; + +export { FlyoutBody } from './body'; +export { FlyoutHeader } from './header'; +export { FlyoutFooter } from './footer'; + +export const useToGetInternalFlyout = () => { + const { browserFields, docValueFields, runtimeMappings } = useSourcererDataView( + SourcererScopeName.detections + ); + const [alert, setAlert] = useState<{ id?: string; indexName?: string }>({ + id: undefined, + indexName: undefined, + }); + + const [loading, detailsData, rawEventData, ecsData, refetchFlyoutData] = useTimelineEventsDetails( + { + docValueFields, + entityType: EntityType.EVENTS, + indexName: alert.indexName ?? '', + eventId: alert.id ?? '', + runtimeMappings, + skip: !alert.id, + } + ); + + const { alertId, isAlert, hostName, ruleName, timestamp } = + useBasicDataFromDetailsData(detailsData); + + const [hostRiskLoading, { data, isModuleEnabled }] = useHostRiskScore({ + filterQuery: hostName ? buildHostNamesFilter([hostName]) : undefined, + pagination: { + cursorStart: 0, + querySize: 1, + }, + }); + + const hostRisk: HostRisk | null = useMemo( + () => + data + ? { + loading: hostRiskLoading, + isModuleEnabled, + result: data, + } + : null, + [data, hostRiskLoading, isModuleEnabled] + ); + + const { + isolateAction, + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + handleIsolationActionSuccess, + showAlertDetails, + showHostIsolationPanel, + } = useHostIsolationTools(); + + const body = useCallback( + ({ isLoading, alert: localAlert }: AlertsTableFlyoutBaseProps) => { + setAlert((prevAlert) => { + if (prevAlert.id !== localAlert._id) { + return { id: localAlert._id, indexName: localAlert._index }; + } + return prevAlert; + }); + + return ( + + ); + }, + [ + alertId, + browserFields, + detailsData, + handleIsolationActionSuccess, + hostName, + hostRisk, + isAlert, + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + isolateAction, + loading, + rawEventData, + showAlertDetails, + ] + ); + + const header = useCallback( + ({ isLoading }: AlertsTableFlyoutBaseProps) => { + return ( + + ); + }, + [ + isAlert, + isHostIsolationPanelOpen, + isolateAction, + loading, + ruleName, + showAlertDetails, + timestamp, + ] + ); + + const footer = useCallback( + ({ isLoading, alert: localAlert }: AlertsTableFlyoutBaseProps) => { + return ( + + ); + }, + [ + detailsData, + ecsData, + isHostIsolationPanelOpen, + loading, + refetchFlyoutData, + showHostIsolationPanel, + ] + ); + + return useMemo( + () => ({ + body, + header, + footer, + }), + [body, header, footer] + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx new file mode 100644 index 0000000000000..9f7019e68b17f --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx @@ -0,0 +1,53 @@ +/* + * 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 { some } from 'lodash/fp'; +import { useMemo } from 'react'; +import { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; +import { getFieldValue } from '../../../../detections/components/host_isolation/helpers'; + +interface GetBasicDataFromDetailsData { + alertId: string; + isAlert: boolean; + hostName: string; + ruleName: string; + timestamp: string; +} + +export const useBasicDataFromDetailsData = ( + data: TimelineEventsDetailsItem[] | null +): GetBasicDataFromDetailsData => { + const isAlert = some({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, data); + + const ruleName = useMemo( + () => getFieldValue({ category: 'kibana', field: 'kibana.alert.rule.name' }, data), + [data] + ); + + const alertId = useMemo(() => getFieldValue({ category: '_id', field: '_id' }, data), [data]); + + const hostName = useMemo( + () => getFieldValue({ category: 'host', field: 'host.name' }, data), + [data] + ); + + const timestamp = useMemo( + () => getFieldValue({ category: 'base', field: '@timestamp' }, data), + [data] + ); + + return useMemo( + () => ({ + alertId, + isAlert, + hostName, + ruleName, + timestamp, + }), + [alertId, hostName, isAlert, ruleName, timestamp] + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index a5de47f5e8913..364239f2625f6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -5,17 +5,9 @@ * 2.0. */ -import { some } from 'lodash/fp'; -import { - EuiButtonEmpty, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiSpacer, - EuiTitle, - EuiText, -} from '@elastic/eui'; -import React, { useState, useCallback, useMemo } from 'react'; -import styled from 'styled-components'; +import { EuiSpacer } from '@elastic/eui'; +import React from 'react'; + import deepEqual from 'fast-deep-equal'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { EntityType } from '@kbn/timelines-plugin/common'; @@ -23,32 +15,11 @@ import { BrowserFields, DocValueFields } from '../../../../common/containers/sou import { ExpandableEvent, ExpandableEventTitle } from './expandable_event'; import { useTimelineEventsDetails } from '../../../containers/details'; import { TimelineTabs } from '../../../../../common/types/timeline'; -import { HostIsolationPanel } from '../../../../detections/components/host_isolation'; -import { EndpointIsolateSuccess } from '../../../../common/components/endpoint/host_isolation'; -import { - ISOLATE_HOST, - UNISOLATE_HOST, -} from '../../../../detections/components/host_isolation/translations'; -import { getFieldValue } from '../../../../detections/components/host_isolation/helpers'; -import { ALERT_DETAILS } from './translations'; -import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; -import { EventDetailsFooter } from './footer'; import { buildHostNamesFilter } from '../../../../../common/search_strategy'; import { useHostRiskScore, HostRisk } from '../../../../risk_score/containers'; - -const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` - .euiFlyoutBody__overflow { - display: flex; - flex: 1; - overflow: hidden; - - .euiFlyoutBody__overflowContent { - flex: 1; - overflow: hidden; - padding: ${({ theme }) => `0 ${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.m}`}; - } - } -`; +import { useHostIsolationTools } from './use_host_isolation_tools'; +import { FlyoutBody, FlyoutHeader, FlyoutFooter } from './flyout'; +import { useBasicDataFromDetailsData } from './helpers'; interface EventDetailsPanelProps { browserFields: BrowserFields; @@ -92,43 +63,17 @@ const EventDetailsPanelComponent: React.FC = ({ } ); - const [isHostIsolationPanelOpen, setIsHostIsolationPanel] = useState(false); - - const [isolateAction, setIsolateAction] = useState<'isolateHost' | 'unisolateHost'>( - 'isolateHost' - ); - - const [isIsolateActionSuccessBannerVisible, setIsIsolateActionSuccessBannerVisible] = - useState(false); - - const showAlertDetails = useCallback(() => { - setIsHostIsolationPanel(false); - setIsIsolateActionSuccessBannerVisible(false); - }, []); - - const showHostIsolationPanel = useCallback((action) => { - if (action === 'isolateHost' || action === 'unisolateHost') { - setIsHostIsolationPanel(true); - setIsolateAction(action); - } - }, []); - - const isAlert = some({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, detailsData); - - const ruleName = useMemo( - () => getFieldValue({ category: 'kibana', field: 'kibana.alert.rule.name' }, detailsData), - [detailsData] - ); - - const alertId = useMemo( - () => getFieldValue({ category: '_id', field: '_id' }, detailsData), - [detailsData] - ); + const { + isolateAction, + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + handleIsolationActionSuccess, + showAlertDetails, + showHostIsolationPanel, + } = useHostIsolationTools(); - const hostName = useMemo( - () => getFieldValue({ category: 'host', field: 'host.name' }, detailsData), - [detailsData] - ); + const { alertId, isAlert, hostName, ruleName, timestamp } = + useBasicDataFromDetailsData(detailsData); const [hostRiskLoading, { data, isModuleEnabled }] = useHostRiskScore({ filterQuery: hostName ? buildHostNamesFilter([hostName]) : undefined, @@ -146,105 +91,53 @@ const EventDetailsPanelComponent: React.FC = ({ } : null; - const timestamp = useMemo( - () => getFieldValue({ category: 'base', field: '@timestamp' }, detailsData), - [detailsData] - ); - - const backToAlertDetailsLink = useMemo(() => { - return ( - <> - showAlertDetails()} - > - -

{ALERT_DETAILS}

-
-
- -

{isolateAction === 'isolateHost' ? ISOLATE_HOST : UNISOLATE_HOST}

-
- - ); - }, [showAlertDetails, isolateAction]); - - const caseDetailsRefresh = useWithCaseDetailsRefresh(); - - const handleIsolationActionSuccess = useCallback(() => { - setIsIsolateActionSuccessBannerVisible(true); - // If a case details refresh ref is defined, then refresh actions and comments - if (caseDetailsRefresh) { - caseDetailsRefresh.refreshCase(); - } - }, [caseDetailsRefresh]); - if (!expandedEvent?.eventId) { return null; } return isFlyoutView ? ( <> - - {isHostIsolationPanelOpen ? ( - backToAlertDetailsLink - ) : ( - - )} - - {isIsolateActionSuccessBannerVisible && ( - - )} - - {isHostIsolationPanelOpen ? ( - - ) : ( - - )} - - - {!isReadOnly && ( - - )} + + + ) : ( <> @@ -268,19 +161,18 @@ const EventDetailsPanelComponent: React.FC = ({ hostRisk={hostRisk} handleOnEventClosed={handleOnEventClosed} /> - {!isReadOnly && ( - - )} + ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx new file mode 100644 index 0000000000000..5667c4e0a6156 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx @@ -0,0 +1,108 @@ +/* + * 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 { useCallback, useMemo, useReducer } from 'react'; + +import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; + +interface HostIsolationStateReducer { + isolateAction: 'isolateHost' | 'unisolateHost'; + isHostIsolationPanelOpen: boolean; + isIsolateActionSuccessBannerVisible: boolean; +} + +type HostIsolationActions = + | { + type: 'setIsHostIsolationPanel'; + isHostIsolationPanelOpen: boolean; + } + | { + type: 'setIsolateAction'; + isolateAction: 'isolateHost' | 'unisolateHost'; + } + | { + type: 'setIsIsolateActionSuccessBannerVisible'; + isIsolateActionSuccessBannerVisible: boolean; + }; + +const initialHostIsolationState: HostIsolationStateReducer = { + isolateAction: 'isolateHost', + isHostIsolationPanelOpen: false, + isIsolateActionSuccessBannerVisible: false, +}; + +function hostIsolationReducer(state: HostIsolationStateReducer, action: HostIsolationActions) { + switch (action.type) { + case 'setIsolateAction': + return { ...state, isolateAction: action.isolateAction }; + case 'setIsHostIsolationPanel': + return { ...state, isHostIsolationPanelOpen: action.isHostIsolationPanelOpen }; + case 'setIsIsolateActionSuccessBannerVisible': + return { + ...state, + isIsolateActionSuccessBannerVisible: action.isIsolateActionSuccessBannerVisible, + }; + default: + throw new Error(); + } +} + +const useHostIsolationTools = () => { + const [ + { isolateAction, isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible }, + dispatch, + ] = useReducer(hostIsolationReducer, initialHostIsolationState); + + const showAlertDetails = useCallback(() => { + dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: false }); + dispatch({ + type: 'setIsIsolateActionSuccessBannerVisible', + isIsolateActionSuccessBannerVisible: false, + }); + }, []); + + const showHostIsolationPanel = useCallback((action) => { + if (action === 'isolateHost' || action === 'unisolateHost') { + dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: true }); + dispatch({ type: 'setIsolateAction', isolateAction: action }); + } + }, []); + + const caseDetailsRefresh = useWithCaseDetailsRefresh(); + + const handleIsolationActionSuccess = useCallback(() => { + dispatch({ + type: 'setIsIsolateActionSuccessBannerVisible', + isIsolateActionSuccessBannerVisible: true, + }); + // If a case details refresh ref is defined, then refresh actions and comments + if (caseDetailsRefresh) { + caseDetailsRefresh.refreshCase(); + } + }, [caseDetailsRefresh]); + + return useMemo( + () => ({ + isolateAction, + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + handleIsolationActionSuccess, + showAlertDetails, + showHostIsolationPanel, + }), + [ + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + isolateAction, + handleIsolationActionSuccess, + showAlertDetails, + showHostIsolationPanel, + ] + ); +}; + +export { useHostIsolationTools }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx index 74c1bdcebb9e3..4996da8008f34 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx @@ -16,6 +16,8 @@ const props = { alert: { [AlertsField.name]: ['one'], [AlertsField.reason]: ['two'], + _id: '0123456789', + _index: '.alerts-default', }, alertsTableConfiguration: { id: 'test', @@ -34,9 +36,11 @@ const props = { externalFlyout: { body: () =>

External flyout body

, }, - internalFlyout: { + useInternalFlyout: () => ({ body: () =>

Internal flyout body

, - }, + header: null, + footer: () => null, + }), getRenderCellValue: () => jest.fn().mockImplementation((rcvProps) => { return `${rcvProps.colIndex}:${rcvProps.rowIndex}`; @@ -82,6 +86,7 @@ describe('AlertsFlyout', () => { for (const configuration of configurations) { const base = { body: () =>
Body
, + footer: () => null, }; it(`should use ${configuration} header configuration`, async () => { @@ -89,10 +94,21 @@ describe('AlertsFlyout', () => { ...props, alertsTableConfiguration: { ...props.alertsTableConfiguration, - [`${configuration}Flyout`]: { - ...base, - header: () =>

Header

, - }, + ...(configuration === AlertsTableFlyoutState.external + ? { + [`${configuration}Flyout`]: { + ...base, + header: () =>

Header

, + footer: () => null, + }, + } + : { + useInternalFlyout: () => ({ + ...base, + header: () =>

Header

, + footer: () => null, + }), + }), }, state: configuration, }; @@ -110,6 +126,17 @@ describe('AlertsFlyout', () => { ...props, alertsTableConfiguration: { ...props.alertsTableConfiguration, + ...(configuration === AlertsTableFlyoutState.external + ? { + [`${configuration}Flyout`]: { + ...base, + }, + } + : { + useInternalFlyout: () => ({ + ...base, + }), + }), [`${configuration}Flyout`]: { ...base, }, @@ -130,10 +157,19 @@ describe('AlertsFlyout', () => { ...props, alertsTableConfiguration: { ...props.alertsTableConfiguration, - [`${configuration}Flyout`]: { - ...base, - footer: () =>
Footer
, - }, + ...(configuration === AlertsTableFlyoutState.external + ? { + [`${configuration}Flyout`]: { + ...base, + footer: () =>
Footer
, + }, + } + : { + useInternalFlyout: () => ({ + ...base, + footer: () =>
Footer
, + }), + }), }, state: configuration, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx index 02c95c50c0202..af278698c565b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { Suspense, lazy } from 'react'; +import React, { Suspense, lazy, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlyout, @@ -56,6 +56,16 @@ export const AlertsFlyout: React.FunctionComponent = ({ let Body: AlertTableFlyoutComponent; let Footer: AlertTableFlyoutComponent; + const { + header: internalHeader, + body: internalBody, + footer: internalFooter, + } = alertsTableConfiguration?.useInternalFlyout?.() ?? { + header: null, + body: null, + footer: null, + }; + switch (state) { case AlertsTableFlyoutState.external: Header = alertsTableConfiguration?.externalFlyout?.header ?? AlertsFlyoutHeader; @@ -63,19 +73,72 @@ export const AlertsFlyout: React.FunctionComponent = ({ Footer = alertsTableConfiguration?.externalFlyout?.footer ?? null; break; case AlertsTableFlyoutState.internal: - Header = alertsTableConfiguration?.internalFlyout?.header ?? AlertsFlyoutHeader; - Body = alertsTableConfiguration?.internalFlyout?.body ?? null; - Footer = alertsTableConfiguration?.internalFlyout?.footer ?? null; + Header = internalHeader ?? AlertsFlyoutHeader; + Body = internalBody ?? null; + Footer = internalFooter ?? null; break; } - const passedProps = { - alert, - isLoading, - }; + const passedProps = useMemo( + () => ({ + alert, + isLoading, + }), + [alert, isLoading] + ); + + const FlyoutBody = useCallback( + () => + Body ? ( + + + + ) : null, + [Body, passedProps] + ); + + const FlyoutFooter = useCallback( + () => + Footer ? ( + +