diff --git a/x-pack/plugins/security_solution/common/cti/constants.ts b/x-pack/plugins/security_solution/common/cti/constants.ts index 4e935f3e497f4..631a13df1ecb1 100644 --- a/x-pack/plugins/security_solution/common/cti/constants.ts +++ b/x-pack/plugins/security_solution/common/cti/constants.ts @@ -9,6 +9,7 @@ import { INDICATOR_DESTINATION_PATH } from '../constants'; export const MATCHED_ATOMIC = 'matched.atomic'; export const MATCHED_FIELD = 'matched.field'; +export const MATCHED_ID = 'matched.id'; export const MATCHED_TYPE = 'matched.type'; export const INDICATOR_MATCH_SUBFIELDS = [MATCHED_ATOMIC, MATCHED_FIELD, MATCHED_TYPE]; @@ -18,11 +19,12 @@ export const INDICATOR_MATCHED_TYPE = `${INDICATOR_DESTINATION_PATH}.${MATCHED_T export const EVENT_DATASET = 'event.dataset'; export const EVENT_REFERENCE = 'event.reference'; +export const EVENT_URL = 'event.url'; export const PROVIDER = 'provider'; export const FIRSTSEEN = 'first_seen'; export const INDICATOR_DATASET = `${INDICATOR_DESTINATION_PATH}.${EVENT_DATASET}`; -export const INDICATOR_EVENT_URL = `${INDICATOR_DESTINATION_PATH}.event.url`; +export const INDICATOR_EVENT_URL = `${INDICATOR_DESTINATION_PATH}.${EVENT_URL}`; export const INDICATOR_FIRSTSEEN = `${INDICATOR_DESTINATION_PATH}.${FIRSTSEEN}`; export const INDICATOR_LASTSEEN = `${INDICATOR_DESTINATION_PATH}.last_seen`; export const INDICATOR_PROVIDER = `${INDICATOR_DESTINATION_PATH}.${PROVIDER}`; @@ -37,13 +39,10 @@ export const CTI_ROW_RENDERER_FIELDS = [ INDICATOR_PROVIDER, ]; -export const SORTED_THREAT_SUMMARY_FIELDS = [ - INDICATOR_MATCHED_FIELD, - INDICATOR_MATCHED_TYPE, - INDICATOR_PROVIDER, - INDICATOR_FIRSTSEEN, - INDICATOR_LASTSEEN, -]; +export enum ENRICHMENT_TYPES { + InvestigationTime = 'investigation_time', + IndicatorMatchRule = 'indicator_match_rule', +} export const EVENT_ENRICHMENT_INDICATOR_FIELD_MAP = { 'file.hash.md5': 'threatintel.indicator.file.hash.md5', @@ -58,6 +57,9 @@ export const EVENT_ENRICHMENT_INDICATOR_FIELD_MAP = { 'registry.path': 'threatintel.indicator.registry.path', }; +export const DEFAULT_EVENT_ENRICHMENT_FROM = 'now-30d'; +export const DEFAULT_EVENT_ENRICHMENT_TO = 'now'; + export const CTI_DEFAULT_SOURCES = [ 'Abuse URL', 'Abuse Malware', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts index f3dee5a21e4c9..7898962b1a72d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts @@ -8,6 +8,7 @@ import { IEsSearchResponse } from 'src/plugins/data/public'; import { + CtiEnrichment, CtiEventEnrichmentRequestOptions, CtiEventEnrichmentStrategyResponse, CtiQueries, @@ -99,11 +100,63 @@ export const buildEventEnrichmentRawResponseMock = (): IEsSearchResponse => ({ }, }); +export const buildEventEnrichmentMock = ( + overrides: Partial = {} +): CtiEnrichment => ({ + '@timestamp': ['2021-05-28T18:33:52.993Z'], + 'agent.ephemeral_id': ['d6b14f65-5bf3-430d-8315-7b5613685979'], + 'agent.hostname': ['rylastic.local'], + 'agent.id': ['ff93aee5-86a1-4a61-b0e6-0cdc313d01b5'], + 'agent.name': ['rylastic.local'], + 'agent.type': ['filebeat'], + 'agent.version': ['8.0.0'], + 'ecs.version': ['1.6.0'], + 'event.category': ['threat'], + 'event.created': ['2021-05-28T18:33:52.993Z'], + 'event.dataset': ['threatintel.abusemalware'], + 'event.ingested': ['2021-05-28T18:33:55.086Z'], + 'event.kind': ['enrichment'], + 'event.module': ['threatintel'], + 'event.reference': [ + 'https://urlhaus-api.abuse.ch/v1/download/15b012e6f626d0f88c2926d2bf4ca394d7b8ee07cc06d2ec05ea76bed3e8a05e/', + ], + 'event.type': ['indicator'], + 'fileset.name': ['abusemalware'], + 'input.type': ['httpjson'], + 'matched.atomic': ['5529de7b60601aeb36f57824ed0e1ae8'], + 'matched.field': ['file.hash.md5'], + 'matched.id': ['31408415b6d5601a92d29b86c2519658f210c194057588ae396d55cc20b3f03d'], + 'matched.index': ['filebeat-8.0.0-2021.05.28-000001'], + 'matched.type': ['investigation_time'], + 'related.hash': [ + '5529de7b60601aeb36f57824ed0e1ae8', + '15b012e6f626d0f88c2926d2bf4ca394d7b8ee07cc06d2ec05ea76bed3e8a05e', + '768:NXSFGJ/ooP6FawrB7Bo1MWnF/jRmhJImp:1SFXIqBo1Mwj2p', + ], + 'service.type': ['threatintel'], + tags: ['threatintel-abusemalware', 'forwarded'], + 'threatintel.indicator.file.hash.md5': ['5529de7b60601aeb36f57824ed0e1ae8'], + 'threatintel.indicator.file.hash.sha256': [ + '15b012e6f626d0f88c2926d2bf4ca394d7b8ee07cc06d2ec05ea76bed3e8a05e', + ], + 'threatintel.indicator.file.hash.ssdeep': [ + '768:NXSFGJ/ooP6FawrB7Bo1MWnF/jRmhJImp:1SFXIqBo1Mwj2p', + ], + 'threatintel.indicator.file.hash.tlsh': [ + 'FFB20B82F6617061C32784E2712F7A46B179B04FD1EA54A0F28CD8E9CFE4CAA1617F1C', + ], + 'threatintel.indicator.file.size': [24738], + 'threatintel.indicator.file.type': ['html'], + 'threatintel.indicator.first_seen': ['2021-05-28T18:33:29.000Z'], + 'threatintel.indicator.type': ['file'], + ...overrides, +}); + export const buildEventEnrichmentResponseMock = ( overrides: Partial = {} ): CtiEventEnrichmentStrategyResponse => ({ ...buildEventEnrichmentRawResponseMock(), - enrichments: [], + enrichments: [buildEventEnrichmentMock()], inspect: { dsl: ['{"mocked": "json"}'] }, totalCount: 0, ...overrides, diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts index 788a44bc5b9f7..69a6841c7c14f 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts @@ -6,6 +6,7 @@ */ import { IEsSearchResponse } from 'src/plugins/data/public'; +import { EVENT_ENRICHMENT_INDICATOR_FIELD_MAP } from '../../../cti/constants'; import { Inspect } from '../../common'; import { RequestBasicOptions } from '..'; @@ -18,9 +19,24 @@ export interface CtiEventEnrichmentRequestOptions extends RequestBasicOptions { } export type CtiEnrichment = Record; +export type EventFields = Record; + +export interface CtiEnrichmentIdentifiers { + id: string | undefined; + field: string | undefined; + value: string | undefined; + type: string | undefined; + provider: string | undefined; +} export interface CtiEventEnrichmentStrategyResponse extends IEsSearchResponse { enrichments: CtiEnrichment[]; - inspect?: Inspect; + inspect: Inspect; totalCount: number; } + +export type EventField = keyof typeof EVENT_ENRICHMENT_INDICATOR_FIELD_MAP; +export const validEventFields = Object.keys(EVENT_ENRICHMENT_INDICATOR_FIELD_MAP) as EventField[]; + +export const isValidEventField = (field: string): field is EventField => + validEventFields.includes(field as EventField); diff --git a/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts b/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts new file mode 100644 index 0000000000000..d7ab3986a14f9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts @@ -0,0 +1,26 @@ +/* + * 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 { getFirstElement } from './data_retrieval'; + +describe('getFirstElement', () => { + it('returns undefined if array is undefined', () => { + expect(getFirstElement(undefined)).toEqual(undefined); + }); + + it('returns undefined if array is empty', () => { + expect(getFirstElement([])).toEqual(undefined); + }); + + it('returns the first element if present', () => { + expect(getFirstElement(['hi mom'])).toEqual('hi mom'); + }); + + it('returns the first element of multiple', () => { + expect(getFirstElement(['hi mom', 'hello world'])).toEqual('hi mom'); + }); +}); diff --git a/x-pack/plugins/security_solution/common/utils/data_retrieval.ts b/x-pack/plugins/security_solution/common/utils/data_retrieval.ts new file mode 100644 index 0000000000000..04b6839b854b4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/utils/data_retrieval.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. + */ + +/** + * Retrieves the first element of the given array. + * + * @param array the array to retrieve a value from + * @returns the first element of the array, or undefined if the array is undefined + */ +export const getFirstElement: (array: T[] | undefined) => T | undefined = (array) => + array ? array[0] : undefined; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts new file mode 100644 index 0000000000000..b03daf74ce247 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts @@ -0,0 +1,193 @@ +/* + * 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 { newThreatIndicatorRule } from '../../objects/rule'; +import { cleanKibana, reload } from '../../tasks/common'; +import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; +import { + JSON_LINES, + TABLE_CELL, + TABLE_ROWS, + THREAT_CONTENT, + THREAT_DETAILS_VIEW, + THREAT_INTEL_TAB, + THREAT_SUMMARY_VIEW, + TITLE, +} from '../../screens/alerts_details'; +import { TIMELINE_FIELD } from '../../screens/rule_details'; +import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; +import { expandFirstAlert, goToManageAlertsDetectionRules } from '../../tasks/alerts'; +import { createCustomIndicatorRule } from '../../tasks/api_calls/rules'; +import { + openJsonView, + openThreatIndicatorDetails, + scrollJsonViewToBottom, +} from '../../tasks/alerts_details'; + +import { ALERTS_URL } from '../../urls/navigation'; +import { addsFieldsToTimeline } from '../../tasks/rule_details'; + +describe('CTI Enrichment', () => { + before(() => { + cleanKibana(); + esArchiverLoad('threat_indicator'); + esArchiverLoad('suspicious_source_event'); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); + goToManageAlertsDetectionRules(); + createCustomIndicatorRule(newThreatIndicatorRule); + reload(); + }); + + after(() => { + esArchiverUnload('threat_indicator'); + esArchiverUnload('suspicious_source_event'); + }); + + beforeEach(() => { + loginAndWaitForPageWithoutDateRange(ALERTS_URL); + goToManageAlertsDetectionRules(); + goToRuleDetails(); + }); + + it('Displays enrichment matched.* fields on the timeline', () => { + const expectedFields = { + 'threat.indicator.matched.atomic': newThreatIndicatorRule.atomic, + 'threat.indicator.matched.type': 'indicator_match_rule', + 'threat.indicator.matched.field': newThreatIndicatorRule.indicatorMappingField, + }; + const fields = Object.keys(expectedFields) as Array; + + addsFieldsToTimeline('threat.indicator.matched', fields); + + fields.forEach((field) => { + cy.get(TIMELINE_FIELD(field)).should('have.text', expectedFields[field]); + }); + }); + + it('Displays persisted enrichments on the JSON view', () => { + const expectedEnrichment = [ + { line: 4, text: ' "threat": {' }, + { + line: 3, + text: + ' "indicator": "{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\",\\"event\\":{\\"reference\\":\\"https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/\\",\\"ingested\\":\\"2021-03-10T14:51:09.809069Z\\",\\"created\\":\\"2021-03-10T14:51:07.663Z\\",\\"kind\\":\\"enrichment\\",\\"module\\":\\"threatintel\\",\\"category\\":\\"threat\\",\\"type\\":\\"indicator\\",\\"dataset\\":\\"threatintel.abusemalware\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"indicator_match_rule\\"}}"', + }, + { line: 2, text: ' }' }, + ]; + + expandFirstAlert(); + openJsonView(); + scrollJsonViewToBottom(); + + cy.get(JSON_LINES).then((elements) => { + const length = elements.length; + expectedEnrichment.forEach((enrichment) => { + cy.wrap(elements) + .eq(length - enrichment.line) + .should('have.text', enrichment.text); + }); + }); + }); + + it('Displays threat indicator details on the threat intel tab', () => { + const expectedThreatIndicatorData = [ + { field: 'event.category', value: 'threat' }, + { field: 'event.created', value: '2021-03-10T14:51:07.663Z' }, + { field: 'event.dataset', value: 'threatintel.abusemalware' }, + { field: 'event.ingested', value: '2021-03-10T14:51:09.809069Z' }, + { field: 'event.kind', value: 'enrichment' }, + { field: 'event.module', value: 'threatintel' }, + { + field: 'event.reference', + value: + 'https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/(opens in a new tab or window)', + }, + { field: 'event.type', value: 'indicator' }, + { field: 'file.hash.md5', value: '9b6c3518a91d23ed77504b5416bfb5b3' }, + { + field: 'file.hash.sha256', + value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', + }, + { + field: 'file.hash.ssdeep', + value: '1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL', + }, + { + field: 'file.hash.tlsh', + value: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE', + }, + { field: 'file.size', value: '80280' }, + { field: 'file.type', value: 'elf' }, + { field: 'first_seen', value: '2021-03-10T08:02:14.000Z' }, + { + field: 'matched.atomic', + value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', + }, + { field: 'matched.field', value: 'myhash.mysha256' }, + { + field: 'matched.id', + value: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f', + }, + { field: 'matched.index', value: 'filebeat-7.12.0-2021.03.10-000001' }, + { field: 'matched.type', value: 'indicator_match_rule' }, + { field: 'type', value: 'file' }, + ]; + + expandFirstAlert(); + openThreatIndicatorDetails(); + + cy.get(THREAT_INTEL_TAB).should('have.text', 'Threat Intel (1)'); + cy.get(THREAT_DETAILS_VIEW).within(() => { + cy.get(TABLE_ROWS).should('have.length', expectedThreatIndicatorData.length); + expectedThreatIndicatorData.forEach((row, index) => { + cy.get(TABLE_ROWS) + .eq(index) + .within(() => { + cy.get(TABLE_CELL).eq(0).should('have.text', row.field); + cy.get(TABLE_CELL).eq(1).should('have.text', row.value); + }); + }); + }); + }); + + describe('with additional indicators', () => { + before(() => { + esArchiverLoad('threat_indicator2'); + }); + + after(() => { + esArchiverUnload('threat_indicator2'); + }); + + it('Displays matched fields from both indicator match rules and investigation time enrichments on Alerts Summary tab', () => { + const indicatorMatchRuleEnrichment = { + field: 'myhash.mysha256', + value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', + }; + const investigationTimeEnrichment = { + field: 'source.ip', + value: '192.168.1.1', + }; + const expectedMatches = [indicatorMatchRuleEnrichment, investigationTimeEnrichment]; + + expandFirstAlert(); + + cy.get(THREAT_SUMMARY_VIEW).within(() => { + cy.get(TABLE_ROWS).should('have.length', expectedMatches.length); + expectedMatches.forEach((row, index) => { + cy.get(TABLE_ROWS) + .eq(index) + .within(() => { + cy.get(TITLE).should('have.text', row.field); + cy.get(THREAT_CONTENT).should('have.text', row.value); + }); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index c2e8a92474814..e1268c52f75d4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -16,16 +16,6 @@ import { ALERT_RULE_VERSION, NUMBER_OF_ALERTS, } from '../../screens/alerts'; -import { - JSON_LINES, - TABLE_CELL, - TABLE_ROWS, - THREAT_CONTENT, - THREAT_DETAILS_VIEW, - THREAT_INTEL_TAB, - THREAT_SUMMARY_VIEW, - TITLE, -} from '../../screens/alerts_details'; import { CUSTOM_RULES_BTN, RISK_SCORE, @@ -60,23 +50,15 @@ import { SCHEDULE_DETAILS, SEVERITY_DETAILS, TAGS_DETAILS, - TIMELINE_FIELD, TIMELINE_TEMPLATE_DETAILS, } from '../../screens/rule_details'; import { INDICATOR_MATCH_ROW_RENDER, PROVIDER_BADGE } from '../../screens/timeline'; - import { - expandFirstAlert, goToManageAlertsDetectionRules, investigateFirstAlertInTimeline, waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded, } from '../../tasks/alerts'; -import { - openJsonView, - openThreatIndicatorDetails, - scrollJsonViewToBottom, -} from '../../tasks/alerts_details'; import { changeRowsPerPageTo100, duplicateFirstRule, @@ -121,7 +103,7 @@ import { import { goBackToRuleDetails, waitForKibana } from '../../tasks/edit_rule'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { addsFieldsToTimeline, goBackToAllRulesTable } from '../../tasks/rule_details'; +import { goBackToAllRulesTable } from '../../tasks/rule_details'; import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation'; @@ -520,170 +502,18 @@ describe('indicator match', () => { cy.get(PROVIDER_BADGE).should('have.length', 3); cy.get(PROVIDER_BADGE).should( 'have.text', - `threat.indicator.matched.atomic: "${newThreatIndicatorRule.atomic}"threat.indicator.matched.type: "${newThreatIndicatorRule.type}"threat.indicator.matched.field: "${newThreatIndicatorRule.indicatorMappingField}"` + `threat.indicator.matched.atomic: "${newThreatIndicatorRule.atomic}"threat.indicator.matched.type: "indicator_match_rule"threat.indicator.matched.field: "${newThreatIndicatorRule.indicatorMappingField}"` ); cy.readFile(threatIndicatorPath).then((threatIndicator) => { cy.get(INDICATOR_MATCH_ROW_RENDER).should( 'have.text', - `threat.indicator.matched.field${newThreatIndicatorRule.indicatorMappingField}${accessibilityText}matched${newThreatIndicatorRule.indicatorMappingField}${newThreatIndicatorRule.atomic}${accessibilityText}threat.indicator.matched.type${newThreatIndicatorRule.type}${accessibilityText}fromthreat.indicator.event.dataset${threatIndicator.value.source.event.dataset}${accessibilityText}:threat.indicator.event.reference${threatIndicator.value.source.event.reference}(opens in a new tab or window)${accessibilityText}` + `threat.indicator.matched.field${newThreatIndicatorRule.indicatorMappingField}${accessibilityText}matched${newThreatIndicatorRule.indicatorMappingField}${newThreatIndicatorRule.atomic}${accessibilityText}threat.indicator.matched.typeindicator_match_rule${accessibilityText}fromthreat.indicator.event.dataset${threatIndicator.value.source.event.dataset}${accessibilityText}:threat.indicator.event.reference${threatIndicator.value.source.event.reference}(opens in a new tab or window)${accessibilityText}` ); }); }); }); - describe('Enrichment', () => { - const fieldSearch = 'threat.indicator.matched'; - const fields = [ - 'threat.indicator.matched.atomic', - 'threat.indicator.matched.type', - 'threat.indicator.matched.field', - ]; - const expectedFieldsText = [ - newThreatIndicatorRule.atomic, - newThreatIndicatorRule.type, - newThreatIndicatorRule.indicatorMappingField, - ]; - - const expectedEnrichment = [ - { line: 4, text: ' "threat": {' }, - { - line: 3, - text: - ' "indicator": "{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\",\\"event\\":{\\"reference\\":\\"https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/\\",\\"ingested\\":\\"2021-03-10T14:51:09.809069Z\\",\\"created\\":\\"2021-03-10T14:51:07.663Z\\",\\"kind\\":\\"enrichment\\",\\"module\\":\\"threatintel\\",\\"category\\":\\"threat\\",\\"type\\":\\"indicator\\",\\"dataset\\":\\"threatintel.abusemalware\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"file\\"}}"', - }, - { line: 2, text: ' }' }, - ]; - - before(() => { - cleanKibana(); - esArchiverLoad('threat_indicator'); - esArchiverLoad('suspicious_source_event'); - loginAndWaitForPageWithoutDateRange(ALERTS_URL); - goToManageAlertsDetectionRules(); - createCustomIndicatorRule(newThreatIndicatorRule); - reload(); - }); - - after(() => { - esArchiverUnload('threat_indicator'); - esArchiverUnload('suspicious_source_event'); - }); - - beforeEach(() => { - loginAndWaitForPageWithoutDateRange(ALERTS_URL); - goToManageAlertsDetectionRules(); - goToRuleDetails(); - }); - - it('Displays matches on the timeline', () => { - addsFieldsToTimeline(fieldSearch, fields); - - fields.forEach((field, index) => { - cy.get(TIMELINE_FIELD(field)).should('have.text', expectedFieldsText[index]); - }); - }); - - it('Displays enrichment on the JSON view', () => { - expandFirstAlert(); - openJsonView(); - scrollJsonViewToBottom(); - - cy.get(JSON_LINES).then((elements) => { - const length = elements.length; - expectedEnrichment.forEach((enrichment) => { - cy.wrap(elements) - .eq(length - enrichment.line) - .should('have.text', enrichment.text); - }); - }); - }); - - it('Displays threat summary data on alerts details', () => { - const expectedThreatSummary = [ - { field: 'matched.field', value: 'myhash.mysha256' }, - { field: 'matched.type', value: 'file' }, - { field: 'first_seen', value: '2021-03-10T08:02:14.000Z' }, - ]; - - expandFirstAlert(); - - cy.get(THREAT_SUMMARY_VIEW).within(() => { - cy.get(TABLE_ROWS).should('have.length', expectedThreatSummary.length); - expectedThreatSummary.forEach((row, index) => { - cy.get(TABLE_ROWS) - .eq(index) - .within(() => { - cy.get(TITLE).should('have.text', row.field); - cy.get(THREAT_CONTENT).should('have.text', row.value); - }); - }); - }); - }); - - it('Displays threat indicator data on the threat intel tab', () => { - const expectedThreatIndicatorData = [ - { field: 'first_seen', value: '2021-03-10T08:02:14.000Z' }, - { field: 'file.size', value: '80280' }, - { field: 'file.type', value: 'elf' }, - { - field: 'file.hash.sha256', - value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', - }, - { - field: 'file.hash.tlsh', - value: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE', - }, - { - field: 'file.hash.ssdeep', - value: - '1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL', - }, - { field: 'file.hash.md5', value: '9b6c3518a91d23ed77504b5416bfb5b3' }, - { field: 'type', value: 'file' }, - { - field: 'event.reference', - value: - 'https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/(opens in a new tab or window)', - }, - { field: 'event.ingested', value: '2021-03-10T14:51:09.809069Z' }, - { field: 'event.created', value: '2021-03-10T14:51:07.663Z' }, - { field: 'event.kind', value: 'enrichment' }, - { field: 'event.module', value: 'threatintel' }, - { field: 'event.category', value: 'threat' }, - { field: 'event.type', value: 'indicator' }, - { field: 'event.dataset', value: 'threatintel.abusemalware' }, - { - field: 'matched.atomic', - value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', - }, - { field: 'matched.field', value: 'myhash.mysha256' }, - { - field: 'matched.id', - value: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f', - }, - { field: 'matched.index', value: 'filebeat-7.12.0-2021.03.10-000001' }, - { field: 'matched.type', value: 'file' }, - ]; - - expandFirstAlert(); - openThreatIndicatorDetails(); - - cy.get(THREAT_INTEL_TAB).should('have.text', 'Threat Intel (1)'); - cy.get(THREAT_DETAILS_VIEW).within(() => { - cy.get(TABLE_ROWS).should('have.length', expectedThreatIndicatorData.length); - expectedThreatIndicatorData.forEach((row, index) => { - cy.get(TABLE_ROWS) - .eq(index) - .within(() => { - cy.get(TABLE_CELL).eq(0).should('have.text', row.field); - cy.get(TABLE_CELL).eq(1).should('have.text', row.value); - }); - }); - }); - }); - }); - describe('Duplicates the indicator rule', () => { beforeEach(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts index 12bef09b8356d..460652cf6f2da 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -19,7 +19,7 @@ export const TABLE_TAB = '[data-test-subj="tableTab"]'; export const TABLE_ROWS = '.euiTableRow'; -export const THREAT_CONTENT = '[data-test-subj^=draggable-content-threat]'; +export const THREAT_CONTENT = '[data-test-subj^=draggable-content-]'; export const THREAT_DETAILS_VIEW = '[data-test-subj="threat-details-view-0"]'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx index b3e70fd17c0e1..76c6b077236f0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { useMountAppended } from '../../utils/use_mount_appended'; -import { getMockTheme } from '../../lib/kibana/kibana_react.mock'; +import { useMountAppended } from '../../../utils/use_mount_appended'; +import { getMockTheme } from '../../../lib/kibana/kibana_react.mock'; import { EmptyThreatDetailsView } from './empty_threat_details_view'; -jest.mock('../../lib/kibana'); +jest.mock('../../../lib/kibana'); describe('EmptyThreatDetailsView', () => { const mount = useMountAppended(); @@ -28,10 +28,6 @@ describe('EmptyThreatDetailsView', () => { }, }); - beforeEach(() => { - jest.clearAllMocks(); - }); - test('renders correct items', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx index c78df92dceb3c..d7e1c4d7754ec 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx @@ -8,8 +8,9 @@ import { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; + +import { useKibana } from '../../../lib/kibana'; import * as i18n from './translations'; -import { useKibana } from '../../lib/kibana'; const EmptyThreatDetailsViewContainer = styled.div` display: flex; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx new file mode 100644 index 0000000000000..042940a1cf036 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx @@ -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 React from 'react'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; + +import * as i18n from './translations'; +import { isInvestigationTimeEnrichment } from './helpers'; + +export const getTooltipTitle = (type: string | undefined) => + isInvestigationTimeEnrichment(type) + ? i18n.INVESTIGATION_TOOLTIP_TITLE + : i18n.INDICATOR_TOOLTIP_TITLE; + +export const getTooltipContent = (type: string | undefined) => + isInvestigationTimeEnrichment(type) + ? i18n.INVESTIGATION_TOOLTIP_CONTENT + : i18n.INDICATOR_TOOLTIP_CONTENT; + +export const EnrichmentIcon: React.FC<{ type: string | undefined }> = ({ type }) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx new file mode 100644 index 0000000000000..858962efa9e83 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx @@ -0,0 +1,477 @@ +/* + * 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 { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; +import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; +import { + filterDuplicateEnrichments, + getEnrichmentFields, + parseExistingEnrichments, +} from './helpers'; + +describe('parseExistingEnrichments', () => { + it('returns an empty array if data is empty', () => { + expect(parseExistingEnrichments([])).toEqual([]); + }); + + it('returns an empty array if data contains no enrichment field', () => { + const data = [ + { + category: 'host', + field: 'host.os.name.text', + isObjectArray: false, + originalValue: ['Mac OS X'], + values: ['Mac OS X'], + }, + ]; + expect(parseExistingEnrichments(data)).toEqual([]); + }); + + it('returns an empty array if enrichment field contains invalid JSON', () => { + const data = [ + { + category: 'threat', + field: 'threat.indicator', + isObjectArray: true, + originalValue: ['whoops'], + values: ['whoops'], + }, + ]; + expect(parseExistingEnrichments(data)).toEqual([]); + }); + + it('returns an array if enrichment field contains valid JSON', () => { + const data = [ + { + category: 'threat', + field: 'threat.indicator', + isObjectArray: true, + originalValue: [ + `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`, + ], + values: [ + `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`, + ], + }, + ]; + + expect(parseExistingEnrichments(data)).toEqual([ + [ + { + category: 'first_seen', + field: 'first_seen', + isObjectArray: false, + originalValue: ['2021-03-21T19:40:19.000Z'], + values: ['2021-03-21T19:40:19.000Z'], + }, + { + category: 'provider', + field: 'provider', + isObjectArray: false, + originalValue: ['geenensp'], + values: ['geenensp'], + }, + { + category: 'ip', + field: 'ip', + isObjectArray: false, + originalValue: ['192.168.1.19'], + values: ['192.168.1.19'], + }, + { + category: 'type', + field: 'type', + isObjectArray: false, + originalValue: ['url'], + values: ['url'], + }, + { + category: 'event', + field: 'event.reference', + isObjectArray: false, + originalValue: ['https://urlhaus.abuse.ch/url/1055419/'], + values: ['https://urlhaus.abuse.ch/url/1055419/'], + }, + { + category: 'event', + field: 'event.ingested', + isObjectArray: false, + originalValue: ['2021-03-08T19:40:44.213673Z'], + values: ['2021-03-08T19:40:44.213673Z'], + }, + { + category: 'event', + field: 'event.created', + isObjectArray: false, + originalValue: ['2021-03-08T19:40:43.160Z'], + values: ['2021-03-08T19:40:43.160Z'], + }, + { + category: 'event', + field: 'event.kind', + isObjectArray: false, + originalValue: ['other'], + values: ['other'], + }, + { + category: 'event', + field: 'event.module', + isObjectArray: false, + originalValue: ['threatintel'], + values: ['threatintel'], + }, + { + category: 'event', + field: 'event.category', + isObjectArray: false, + originalValue: ['threat'], + values: ['threat'], + }, + { + category: 'event', + field: 'event.type', + isObjectArray: false, + originalValue: ['indicator'], + values: ['indicator'], + }, + { + category: 'event', + field: 'event.dataset', + isObjectArray: false, + originalValue: ['threatintel.abuseurl'], + values: ['threatintel.abuseurl'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['192.168.1.19'], + values: ['192.168.1.19'], + }, + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.ip'], + values: ['host.ip'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['0SIZMnoB_Blp1Ib9ZYHU'], + values: ['0SIZMnoB_Blp1Ib9ZYHU'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['filebeat-8.0.0-2021.05.28-000001'], + values: ['filebeat-8.0.0-2021.05.28-000001'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['url'], + values: ['url'], + }, + ], + ]); + }); + + it('returns multiple arrays for multiple enrichments', () => { + const data = [ + { + category: 'threat', + field: 'threat.indicator', + isObjectArray: true, + originalValue: [ + `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"other","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"iiL9NHoB_Blp1Ib9yoJo","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`, + `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`, + ], + values: [ + `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"other","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"iiL9NHoB_Blp1Ib9yoJo","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`, + `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`, + ], + }, + ]; + + expect(parseExistingEnrichments(data)).toEqual([ + expect.arrayContaining([ + { + category: 'first_seen', + field: 'first_seen', + isObjectArray: false, + originalValue: ['2021-03-21T19:40:19.000Z'], + values: ['2021-03-21T19:40:19.000Z'], + }, + { + category: 'provider', + field: 'provider', + isObjectArray: false, + originalValue: ['other'], + values: ['other'], + }, + { + category: 'ip', + field: 'ip', + isObjectArray: false, + originalValue: ['192.168.1.19'], + values: ['192.168.1.19'], + }, + { + category: 'type', + field: 'type', + isObjectArray: false, + originalValue: ['url'], + values: ['url'], + }, + { + category: 'event', + field: 'event.reference', + isObjectArray: false, + originalValue: ['https://urlhaus.abuse.ch/url/1055419/'], + values: ['https://urlhaus.abuse.ch/url/1055419/'], + }, + { + category: 'event', + field: 'event.ingested', + isObjectArray: false, + originalValue: ['2021-03-08T19:40:44.213673Z'], + values: ['2021-03-08T19:40:44.213673Z'], + }, + { + category: 'event', + field: 'event.module', + isObjectArray: false, + originalValue: ['threatintel'], + values: ['threatintel'], + }, + { + category: 'event', + field: 'event.category', + isObjectArray: false, + originalValue: ['threat'], + values: ['threat'], + }, + { + category: 'event', + field: 'event.type', + isObjectArray: false, + originalValue: ['indicator'], + values: ['indicator'], + }, + { + category: 'event', + field: 'event.dataset', + isObjectArray: false, + originalValue: ['threatintel.abuseurl'], + values: ['threatintel.abuseurl'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['192.168.1.19'], + values: ['192.168.1.19'], + }, + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.ip'], + values: ['host.ip'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['iiL9NHoB_Blp1Ib9yoJo'], + values: ['iiL9NHoB_Blp1Ib9yoJo'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['filebeat-8.0.0-2021.05.28-000001'], + values: ['filebeat-8.0.0-2021.05.28-000001'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['url'], + values: ['url'], + }, + ]), + expect.arrayContaining([ + { + category: 'first_seen', + field: 'first_seen', + isObjectArray: false, + originalValue: ['2021-03-21T19:40:19.000Z'], + values: ['2021-03-21T19:40:19.000Z'], + }, + { + category: 'provider', + field: 'provider', + isObjectArray: false, + originalValue: ['geenensp'], + values: ['geenensp'], + }, + { + category: 'ip', + field: 'ip', + isObjectArray: false, + originalValue: ['192.168.1.19'], + values: ['192.168.1.19'], + }, + { + category: 'type', + field: 'type', + isObjectArray: false, + originalValue: ['url'], + values: ['url'], + }, + { + category: 'event', + field: 'event.reference', + isObjectArray: false, + originalValue: ['https://urlhaus.abuse.ch/url/1055419/'], + values: ['https://urlhaus.abuse.ch/url/1055419/'], + }, + { + category: 'event', + field: 'event.ingested', + isObjectArray: false, + originalValue: ['2021-03-08T19:40:44.213673Z'], + values: ['2021-03-08T19:40:44.213673Z'], + }, + { + category: 'event', + field: 'event.module', + isObjectArray: false, + originalValue: ['threatintel'], + values: ['threatintel'], + }, + { + category: 'event', + field: 'event.category', + isObjectArray: false, + originalValue: ['threat'], + values: ['threat'], + }, + { + category: 'event', + field: 'event.type', + isObjectArray: false, + originalValue: ['indicator'], + values: ['indicator'], + }, + { + category: 'event', + field: 'event.dataset', + isObjectArray: false, + originalValue: ['threatintel.abuseurl'], + values: ['threatintel.abuseurl'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['192.168.1.19'], + values: ['192.168.1.19'], + }, + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.ip'], + values: ['host.ip'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['0SIZMnoB_Blp1Ib9ZYHU'], + values: ['0SIZMnoB_Blp1Ib9ZYHU'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['filebeat-8.0.0-2021.05.28-000001'], + values: ['filebeat-8.0.0-2021.05.28-000001'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['url'], + values: ['url'], + }, + ]), + ]); + }); +}); + +describe('filterDuplicateEnrichments', () => { + it('returns an empty array if given one', () => { + expect(filterDuplicateEnrichments([])).toEqual([]); + }); + + it('returns the existing enrichment if given both that and an investigation-time enrichment for the same indicator and field', () => { + const existingEnrichment = buildEventEnrichmentMock({ + 'matched.type': [ENRICHMENT_TYPES.IndicatorMatchRule], + }); + const investigationEnrichment = buildEventEnrichmentMock({ + 'matched.type': [ENRICHMENT_TYPES.InvestigationTime], + }); + expect(filterDuplicateEnrichments([existingEnrichment, investigationEnrichment])).toEqual([ + existingEnrichment, + ]); + }); + + it('includes two enrichments from the same indicator if it matched different fields', () => { + const enrichments = [ + buildEventEnrichmentMock(), + buildEventEnrichmentMock({ + 'matched.field': ['other.field'], + }), + ]; + expect(filterDuplicateEnrichments(enrichments)).toEqual(enrichments); + }); +}); + +describe('getEnrichmentFields', () => { + it('returns an empty object if items is empty', () => { + expect(getEnrichmentFields([])).toEqual({}); + }); + + it('returns an object of event fields and values', () => { + const data = [ + { + category: 'source', + field: 'source.ip', + isObjectArray: false, + originalValue: ['192.168.1.1'], + values: ['192.168.1.1'], + }, + { + category: 'event', + field: 'event.reference', + isObjectArray: false, + originalValue: ['https://urlhaus.abuse.ch/url/1055419/'], + values: ['https://urlhaus.abuse.ch/url/1055419/'], + }, + ]; + expect(getEnrichmentFields(data)).toEqual({ + 'source.ip': '192.168.1.1', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx new file mode 100644 index 0000000000000..b048bb076e2d3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx @@ -0,0 +1,123 @@ +/* + * 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 { groupBy } from 'lodash'; +import { + DEFAULT_INDICATOR_SOURCE_PATH, + INDICATOR_DESTINATION_PATH, +} from '../../../../../common/constants'; +import { + ENRICHMENT_TYPES, + MATCHED_ATOMIC, + MATCHED_FIELD, + MATCHED_ID, + MATCHED_TYPE, + PROVIDER, +} from '../../../../../common/cti/constants'; +import { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; +import { + CtiEnrichment, + CtiEnrichmentIdentifiers, + EventFields, + isValidEventField, +} from '../../../../../common/search_strategy/security_solution/cti'; +import { getFirstElement } from '../../../../../common/utils/data_retrieval'; +import { getDataFromSourceHits } from '../../../../../common/utils/field_formatters'; + +export const isInvestigationTimeEnrichment = (type: string | undefined) => + type === ENRICHMENT_TYPES.InvestigationTime; + +export const parseExistingEnrichments = ( + data: TimelineEventsDetailsItem[] +): TimelineEventsDetailsItem[][] => { + const threatIndicatorField = data.find( + ({ field, originalValue }) => field === INDICATOR_DESTINATION_PATH && originalValue + ); + if (!threatIndicatorField) { + return []; + } + + const { originalValue } = threatIndicatorField; + const enrichmentStrings = Array.isArray(originalValue) ? originalValue : [originalValue]; + + return enrichmentStrings.reduce( + (enrichments, enrichmentString) => { + try { + const enrichment = getDataFromSourceHits(JSON.parse(enrichmentString)); + enrichments.push(enrichment); + } catch (e) { + // omit failed parse + } + return enrichments; + }, + [] + ); +}; + +export const timelineDataToEnrichment = (data: TimelineEventsDetailsItem[]): CtiEnrichment => + data.reduce((acc, item) => { + acc[item.field] = item.originalValue; + return acc; + }, {}); + +export const getEnrichmentValue = (enrichment: CtiEnrichment, field: string) => + getFirstElement(enrichment[field]) as string | undefined; + +/** + * These fields (e.g. 'x') may be in one of two keys depending on whether it's + * a new enrichment ('threatintel.indicator.x') or an old indicator alert + * (simply 'x'). Once enrichment has been normalized and we support the new ECS + * fields, this value should always be 'indicator.x'; + */ +export const getShimmedIndicatorValue = (enrichment: CtiEnrichment, field: string) => + getEnrichmentValue(enrichment, field) || + getEnrichmentValue(enrichment, `${DEFAULT_INDICATOR_SOURCE_PATH}.${field}`); + +export const getEnrichmentIdentifiers = (enrichment: CtiEnrichment): CtiEnrichmentIdentifiers => ({ + id: getEnrichmentValue(enrichment, MATCHED_ID), + field: getEnrichmentValue(enrichment, MATCHED_FIELD), + value: getEnrichmentValue(enrichment, MATCHED_ATOMIC), + type: getEnrichmentValue(enrichment, MATCHED_TYPE), + provider: getShimmedIndicatorValue(enrichment, PROVIDER), +}); + +const buildEnrichmentId = (enrichment: CtiEnrichment): string => { + const { id, field } = getEnrichmentIdentifiers(enrichment); + return `${id}${field}`; +}; + +/** + * This function receives an array of enrichments and removes + * investigation-time enrichments if that exact indicator already exists + * elsewhere in the list. + * + * @param enrichments {@type CtiEnrichment[]} + */ +export const filterDuplicateEnrichments = (enrichments: CtiEnrichment[]): CtiEnrichment[] => { + if (enrichments.length < 2) { + return enrichments; + } + const enrichmentsById = groupBy(enrichments, buildEnrichmentId); + + return Object.values(enrichmentsById).map( + (enrichmentGroup) => + enrichmentGroup.find( + (enrichment) => !isInvestigationTimeEnrichment(getEnrichmentValue(enrichment, MATCHED_TYPE)) + ) ?? enrichmentGroup[0] + ); +}; + +export const getEnrichmentFields = (items: TimelineEventsDetailsItem[]): EventFields => + items.reduce((fields, item) => { + if (isValidEventField(item.field)) { + const value = getFirstElement(item.originalValue); + if (value) { + return { ...fields, [item.field]: value }; + } + } + return fields; + }, {}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx new file mode 100644 index 0000000000000..0113dde96a4b6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.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 { mount } from 'enzyme'; + +import { TestProviders } from '../../../mock'; +import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; +import { FIRSTSEEN } from '../../../../../common/cti/constants'; +import { ThreatDetailsView } from './threat_details_view'; + +describe('ThreatDetailsView', () => { + it('renders a detail view for each enrichment', () => { + const enrichments = [ + buildEventEnrichmentMock(), + buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }), + ]; + + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj^="threat-details-view"]').hostNodes()).toHaveLength( + enrichments.length + ); + }); + + it('renders an empty view if there are no enrichments', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); + }); + + it('renders anchor links for event.url and event.reference', () => { + const enrichments = [ + buildEventEnrichmentMock({ + 'event.url': ['http://foo.bar'], + 'event.reference': ['http://foo.baz'], + }), + ]; + const wrapper = mount( + + + + ); + expect(wrapper.find('a').length).toEqual(2); + }); + + it('orders enrichments by first_seen descending', () => { + const mostRecentDate = '2021-04-25T18:17:00.000Z'; + const olderDate = '2021-03-25T18:17:00.000Z'; + // this simulates a legacy enrichment from the old indicator match rule, + // where first_seen is available at the top level + const existingEnrichment = buildEventEnrichmentMock({ + first_seen: [mostRecentDate], + }); + delete existingEnrichment['threatintel.indicator.first_seen']; + const newEnrichment = buildEventEnrichmentMock({ + 'matched.id': ['other.id'], + 'threatintel.indicator.first_seen': [olderDate], + }); + const enrichments = [existingEnrichment, newEnrichment]; + + const wrapper = mount( + + + + ); + + const firstSeenRows = wrapper + .find('.euiTableRow') + .hostNodes() + .filterWhere((node) => node.text().includes(FIRSTSEEN)); + expect(firstSeenRows.map((node) => node.text())).toEqual([ + `first_seen${mostRecentDate}`, + `first_seen${olderDate}`, + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx new file mode 100644 index 0000000000000..d5e985c5757a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx @@ -0,0 +1,163 @@ +/* + * 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 { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiSpacer, + EuiToolTip, + EuiLink, + EuiText, + EuiTextColor, +} from '@elastic/eui'; +import React, { Fragment } from 'react'; + +import { StyledEuiInMemoryTable } from '../summary_view'; +import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from '../helpers'; +import { EmptyThreatDetailsView } from './empty_threat_details_view'; +import { FIRSTSEEN, EVENT_URL, EVENT_REFERENCE } from '../../../../../common/cti/constants'; +import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; +import { getFirstElement } from '../../../../../common/utils/data_retrieval'; +import { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti'; +import { + getShimmedIndicatorValue, + isInvestigationTimeEnrichment, + getEnrichmentIdentifiers, +} from './helpers'; +import * as i18n from './translations'; +import { EnrichmentIcon } from './enrichment_icon'; +import { QUERY_ID } from '../../../containers/cti/event_enrichment/use_investigation_enrichment'; +import { InspectButton } from '../../inspect'; + +const getFirstSeen = (enrichment: CtiEnrichment): number => { + const firstSeenValue = getShimmedIndicatorValue(enrichment, FIRSTSEEN); + const firstSeenDate = Date.parse(firstSeenValue ?? 'no date'); + return Number.isInteger(firstSeenDate) ? firstSeenDate : new Date(-1).valueOf(); +}; + +const ThreatDetailsHeader: React.FC<{ + field: string | undefined; + value: string | undefined; + provider: string | undefined; + type: string | undefined; +}> = ({ field, value, provider, type }) => ( + <> + + + + + + + + {field} {value} + + + {provider && ( + <> + + + {i18n.PROVIDER_PREPOSITION} {provider} + + + + )} + + + + + + {isInvestigationTimeEnrichment(type) && ( + + + + + + )} + +); + +const ThreatDetailsDescription: React.FC = ({ + fieldName, + value, +}) => { + const tooltipChild = [EVENT_URL, EVENT_REFERENCE].includes(fieldName) ? ( + + {value} + + ) : ( + {value} + ); + return ( + + + {fieldName} + + + } + > + {tooltipChild} + + ); +}; + +const columns: Array> = getSummaryColumns(ThreatDetailsDescription); + +const buildThreatDetailsItems = (enrichment: CtiEnrichment) => + Object.keys(enrichment) + .sort() + .map((field) => { + const displayField = field.startsWith(DEFAULT_INDICATOR_SOURCE_PATH) + ? field.replace(`${DEFAULT_INDICATOR_SOURCE_PATH}.`, '') + : field; + + return { + title: displayField, + description: { + fieldName: field, + value: getFirstElement(enrichment[field]), + }, + }; + }); + +const ThreatDetailsViewComponent: React.FC<{ + enrichments: CtiEnrichment[]; +}> = ({ enrichments }) => { + if (enrichments.length < 1) { + return ; + } + + const sortedEnrichments = enrichments.sort((a, b) => getFirstSeen(b) - getFirstSeen(a)); + + return ( + <> + + {sortedEnrichments.map((enrichment, index) => { + const { id, field, provider, type, value } = getEnrichmentIdentifiers(enrichment); + + return ( + + + + {index < sortedEnrichments.length - 1 && } + + ); + })} + + ); +}; + +export const ThreatDetailsView = React.memo(ThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx new file mode 100644 index 0000000000000..bf6c4b9594344 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx @@ -0,0 +1,41 @@ +/* + * 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 { ThreatSummaryView } from './threat_summary_view'; +import { TestProviders } from '../../../mock'; +import { useMountAppended } from '../../../utils/use_mount_appended'; +import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; + +jest.mock('../../../../timelines/components/timeline/body/renderers/formatted_field'); + +describe('ThreatSummaryView', () => { + const mount = useMountAppended(); + const eventId = '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31'; + const timelineId = 'detections-page'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders a row for each enrichment', () => { + const enrichments = [ + buildEventEnrichmentMock(), + buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }), + ]; + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="threat-summary-view"] .euiTableRow')).toHaveLength( + enrichments.length + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx new file mode 100644 index 0000000000000..4a6c9ec48bcbc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx @@ -0,0 +1,141 @@ +/* + * 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 styled from 'styled-components'; +import React from 'react'; +import { EuiBasicTableColumn, EuiText, EuiTitle } from '@elastic/eui'; + +import * as i18n from './translations'; +import { StyledEuiInMemoryTable } from '../summary_view'; +import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; +import { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti'; +import { getEnrichmentIdentifiers } from './helpers'; +import { EnrichmentIcon } from './enrichment_icon'; + +export interface ThreatSummaryItem { + title: { + title: string | undefined; + type: string | undefined; + }; + description: { + timelineId: string; + eventId: string; + fieldName: string | undefined; + index: number; + value: string | undefined; + provider: string | undefined; + }; +} + +const RightMargin = styled.span` + margin-right: ${({ theme }) => theme.eui.paddingSizes.s}; +`; + +const EnrichmentTitle: React.FC = ({ title, type }) => ( + <> + + + + +
{title}
+
+ +); + +const EnrichmentDescription: React.FC = ({ + timelineId, + eventId, + fieldName, + index, + value, + provider, +}) => { + const key = `alert-details-value-formatted-field-value-${timelineId}-${eventId}-${fieldName}-${value}-${index}-${provider}`; + return ( + <> + + + + {provider && ( + <> + + + {i18n.PROVIDER_PREPOSITION} + + + + + {provider} + + + + )} + + ); +}; + +const buildThreatSummaryItems = ( + enrichments: CtiEnrichment[], + timelineId: string, + eventId: string +) => { + return enrichments.map((enrichment, index) => { + const { field, type, value, provider } = getEnrichmentIdentifiers(enrichment); + + return { + title: { + title: field, + type, + }, + description: { + eventId, + fieldName: field, + index, + provider, + timelineId, + value, + }, + }; + }); +}; + +const columns: Array> = [ + { + field: 'title', + truncateText: false, + render: EnrichmentTitle, + width: '160px', + name: '', + }, + { + field: 'description', + truncateText: false, + render: EnrichmentDescription, + name: '', + }, +]; + +const ThreatSummaryViewComponent: React.FC<{ + enrichments: CtiEnrichment[]; + timelineId: string; + eventId: string; +}> = ({ enrichments, timelineId, eventId }) => ( + +); + +export const ThreatSummaryView = React.memo(ThreatSummaryViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts new file mode 100644 index 0000000000000..a0c247db927ce --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts @@ -0,0 +1,71 @@ +/* + * 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 PROVIDER_PREPOSITION = i18n.translate( + 'xpack.securitySolution.eventDetails.ctiSummary.providerPreposition', + { + defaultMessage: 'from', + } +); + +export const INDICATOR_TOOLTIP_TITLE = i18n.translate( + 'xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipTitle', + { + defaultMessage: 'Indicator rule enrichment', + } +); + +export const INVESTIGATION_TOOLTIP_TITLE = i18n.translate( + 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipTitle', + { + defaultMessage: 'Investigation time enrichment', + } +); + +export const INDICATOR_TOOLTIP_CONTENT = i18n.translate( + 'xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipContent', + { + defaultMessage: + 'This field matched a known indicator, and was enriched by an indicator match rule. See more details on the Threat Intel tab.', + } +); + +export const INVESTIGATION_TOOLTIP_CONTENT = i18n.translate( + 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipContent', + { + defaultMessage: + 'This field matched a known indicator; see more details on the Threat Intel tab.', + } +); + +export const NO_ENRICHMENT_FOUND = i18n.translate( + 'xpack.securitySolution.alertDetails.noEnrichmentFound', + { + defaultMessage: 'No Threat Intel Enrichment Found', + } +); + +export const IF_CTI_NOT_ENABLED = i18n.translate( + 'xpack.securitySolution.alertDetails.ifCtiNotEnabled', + { + defaultMessage: + "If you haven't enabled any threat intelligence sources and want to learn more about this capability, ", + } +); + +export const CHECK_DOCS = i18n.translate('xpack.securitySolution.alertDetails.checkDocs', { + defaultMessage: 'please check out our documentation.', +}); + +export const INVESTIGATION_QUERY_TITLE = i18n.translate( + 'xpack.securitySolution.alertDetails.investigationTimeQueryTitle', + { + defaultMessage: 'Investigation time enrichment', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 6aff259d8220e..f599cfa242dea 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -19,8 +19,10 @@ import { useMountAppended } from '../../utils/use_mount_appended'; import { mockAlertDetailsData } from './__mocks__'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { TimelineTabs } from '../../../../common/types/timeline'; +import { useInvestigationTimeEnrichment } from '../../containers/cti/event_enrichment'; jest.mock('../../../common/lib/kibana'); +jest.mock('../../containers/cti/event_enrichment'); jest.mock('../link_to'); describe('EventDetails', () => { @@ -46,6 +48,7 @@ describe('EventDetails', () => { let wrapper: ReactWrapper; let alertsWrapper: ReactWrapper; beforeAll(async () => { + (useInvestigationTimeEnrichment as jest.Mock).mockReturnValue({}); wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index c4092214633e5..d07cdd81aa5f4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -5,27 +5,37 @@ * 2.0. */ -import { EuiTabbedContent, EuiTabbedContentTab, EuiSpacer } from '@elastic/eui'; +import { + EuiTabbedContent, + EuiTabbedContentTab, + EuiSpacer, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiLoadingSpinner, +} from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { EventFieldsBrowser } from './event_fields_browser'; import { JsonView } from './json_view'; -import { ThreatSummaryView } from './threat_summary_view'; -import { ThreatDetailsView } from './threat_details_view'; +import { ThreatSummaryView } from './cti_details/threat_summary_view'; +import { ThreatDetailsView } from './cti_details/threat_details_view'; import * as i18n from './translations'; import { AlertSummaryView } from './alert_summary_view'; import { BrowserFields } from '../../containers/source'; +import { useInvestigationTimeEnrichment } from '../../containers/cti/event_enrichment'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import { TimelineTabs } from '../../../../common/types/timeline'; -import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants'; -import { getDataFromSourceHits } from '../../../../common/utils/field_formatters'; +import { + filterDuplicateEnrichments, + getEnrichmentFields, + parseExistingEnrichments, + timelineDataToEnrichment, +} from './cti_details/helpers'; -interface EventViewTab { - id: EventViewId; - name: string; - content: JSX.Element; -} +type EventViewTab = EuiTabbedContentTab; export type EventViewId = | EventsViewType.tableView @@ -90,23 +100,33 @@ const EventDetailsComponent: React.FC = ({ (tab: EuiTabbedContentTab) => setSelectedTabId(tab.id as EventViewId), [setSelectedTabId] ); + const viewThreatIntelTab = useCallback(() => setSelectedTabId(EventsViewType.threatIntelView), [ + setSelectedTabId, + ]); - const threatData = useMemo(() => { - if (isAlert && data) { - const threatIndicator = data.find( - ({ field, originalValue }) => field === INDICATOR_DESTINATION_PATH && originalValue - ); - if (!threatIndicator) return []; - const { originalValue } = threatIndicator; - const values = Array.isArray(originalValue) ? originalValue : [originalValue]; - return values.map((value) => getDataFromSourceHits(JSON.parse(value))); + const eventFields = useMemo(() => getEnrichmentFields(data ?? []), [data]); + const existingEnrichments = useMemo( + () => + isAlert + ? parseExistingEnrichments(data).map((enrichmentData) => + timelineDataToEnrichment(enrichmentData) + ) + : [], + [data, isAlert] + ); + const { + loading: enrichmentsLoading, + result: enrichmentsResponse, + } = useInvestigationTimeEnrichment(eventFields); + const allEnrichments = useMemo(() => { + if (enrichmentsLoading || !enrichmentsResponse?.enrichments) { + return existingEnrichments; } - return []; - }, [data, isAlert]); - - const threatCount = useMemo(() => threatData.length, [threatData.length]); + return filterDuplicateEnrichments([...existingEnrichments, ...enrichmentsResponse.enrichments]); + }, [enrichmentsLoading, enrichmentsResponse, existingEnrichments]); + const enrichmentCount = allEnrichments.length; - const summaryTab = useMemo( + const summaryTab: EventViewTab | undefined = useMemo( () => isAlert ? { @@ -120,15 +140,44 @@ const EventDetailsComponent: React.FC = ({ eventId: id, browserFields, timelineId, - title: threatCount ? i18n.ALERT_SUMMARY : undefined, + title: i18n.ALERT_SUMMARY, }} /> - {threatCount > 0 && } + {enrichmentsLoading && ( + <> + + + )} + {enrichmentCount > 0 && ( + <> + + + + + {i18n.VIEW_CTI_DATA} + + + + )} ), } : undefined, - [browserFields, data, id, isAlert, timelineId, threatCount] + [ + isAlert, + data, + id, + browserFields, + timelineId, + enrichmentsLoading, + enrichmentCount, + allEnrichments, + viewThreatIntelTab, + ] ); const threatIntelTab = useMemo( @@ -137,11 +186,16 @@ const EventDetailsComponent: React.FC = ({ ? { id: EventsViewType.threatIntelView, 'data-test-subj': 'threatIntelTab', - name: `${i18n.THREAT_INTEL} (${threatCount})`, - content: , + name: ( + + {`${i18n.THREAT_INTEL} `} + {enrichmentsLoading ? : `(${enrichmentCount})`} + + ), + content: , } : undefined, - [isAlert, threatCount, threatData] + [allEnrichments, enrichmentCount, enrichmentsLoading, isAlert] ); const tableTab = useMemo( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index 8392be420a2c5..6002f66da4309 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -64,16 +64,6 @@ export interface AlertSummaryRow { }; } -export interface ThreatSummaryRow { - title: string; - description: { - contextId: string; - eventId: string; - fieldName: string; - values: string[]; - }; -} - export interface ThreatDetailsRow { title: string; description: { @@ -82,7 +72,7 @@ export interface ThreatDetailsRow { }; } -export type SummaryRow = AlertSummaryRow | ThreatSummaryRow | ThreatDetailsRow; +export type SummaryRow = AlertSummaryRow | ThreatDetailsRow; export const getColumnHeaderFromBrowserField = ({ browserField, @@ -215,7 +205,6 @@ getTitle.displayName = 'getTitle'; export const getSummaryColumns = ( DescriptionComponent: - | React.FC | React.FC | React.FC ): Array> => { 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 1dda40ae4b19d..0e846f3f6f699 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 @@ -12,19 +12,13 @@ import styled from 'styled-components'; import { SummaryRow } from './helpers'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` +export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` .euiTableHeaderCell { border: none; } .euiTableRowCell { border: none; } - - .euiTableCellContent { - display: flex; - flex-direction: column; - align-items: flex-start; - } `; const StyledEuiTitle = styled(EuiTitle)` diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx deleted file mode 100644 index 4b2f56a205042..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx +++ /dev/null @@ -1,114 +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 { ThreatDetailsView } from './threat_details_view'; - -import { TestProviders } from '../../mock'; -import { useMountAppended } from '../../utils/use_mount_appended'; - -jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { - return { - useRuleAsync: jest.fn(), - }; -}); - -const mostRecentDate = '2021-04-25T18:17:00.000Z'; - -const threatData = [ - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['test_field_2'], - values: ['test_field_2'], - }, - { - category: 'first_seen', - field: 'first_seen', - isObjectArray: false, - originalValue: ['2019-04-25T18:17:00.000Z'], - values: ['2019-04-25T18:17:00.000Z'], - }, - { - category: 'event', - field: 'event.reference', - isObjectArray: false, - originalValue: ['https://test.com/'], - values: ['https://test.com/'], - }, - { - category: 'event', - field: 'event.url', - isObjectArray: false, - originalValue: ['https://test2.com/'], - values: ['https://test2.com/'], - }, - ], - [ - { - category: 'first_seen', - field: 'first_seen', - isObjectArray: false, - originalValue: [mostRecentDate], - values: [mostRecentDate], - }, - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['test_field'], - values: ['test_field'], - }, - ], -]; - -describe('ThreatDetailsView', () => { - const mount = useMountAppended(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('render correct items', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="threat-details-view-0"]').exists()).toEqual(true); - }); - - test('renders empty view if there are no items', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - - test('renders link for event.url and event.reference', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('a').length).toEqual(2); - }); - - test('orders items by first_seen', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('.euiToolTipAnchor span').at(0).text()).toEqual(mostRecentDate); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx deleted file mode 100644 index 0f577200b7b47..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx +++ /dev/null @@ -1,122 +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 { - EuiBasicTableColumn, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiSpacer, - EuiToolTip, - EuiLink, -} from '@elastic/eui'; -import React from 'react'; - -import { isEmpty } from 'fp-ts/Array'; -import { SummaryView } from './summary_view'; -import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from './helpers'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants'; -import { - FIRSTSEEN, - INDICATOR_EVENT_URL, - INDICATOR_REFERENCE, -} from '../../../../common/cti/constants'; -import { EmptyThreatDetailsView } from './empty_threat_details_view'; - -const ThreatDetailsDescription: React.FC = ({ - fieldName, - value, -}) => { - const tooltipChild = [INDICATOR_EVENT_URL, INDICATOR_REFERENCE].some( - (field) => field === fieldName - ) ? ( - - {value} - - ) : ( - {value} - ); - return ( - - - {fieldName} - - - } - > - {tooltipChild} - - ); -}; - -const summaryColumns: Array> = getSummaryColumns( - ThreatDetailsDescription -); - -const getISOStringFromThreatDataItem = (threatDataItem: TimelineEventsDetailsItem[]) => { - const firstSeen = threatDataItem.find( - (item: TimelineEventsDetailsItem) => item.field === FIRSTSEEN - ); - if (firstSeen) { - const { originalValue } = firstSeen; - const firstSeenValue = Array.isArray(originalValue) ? originalValue[0] : originalValue; - if (!Number.isNaN(Date.parse(firstSeenValue))) { - return firstSeenValue; - } - } - return new Date(-1).toString(); -}; - -const getThreatDetailsRowsArray = (threatData: TimelineEventsDetailsItem[][]) => - threatData - .sort( - (a, b) => - Date.parse(getISOStringFromThreatDataItem(b)) - - Date.parse(getISOStringFromThreatDataItem(a)) - ) - .map((items) => - items.map(({ field, originalValue }) => ({ - title: field, - description: { - fieldName: `${INDICATOR_DESTINATION_PATH}.${field}`, - value: Array.isArray(originalValue) ? originalValue[0] : originalValue, - }, - })) - ); - -const ThreatDetailsViewComponent: React.FC<{ - threatData: TimelineEventsDetailsItem[][]; -}> = ({ threatData }) => { - const threatDetailsRowsArray = getThreatDetailsRowsArray(threatData); - return isEmpty(threatDetailsRowsArray) || isEmpty(threatDetailsRowsArray[0]) ? ( - - ) : ( - <> - {threatDetailsRowsArray.map((summaryRows, index, arr) => { - const key = summaryRows.find((threat) => threat.title === 'matched.id')?.description - .value[0]; - return ( -
- {index === 0 && } - - {index < arr.length - 1 && } -
- ); - })} - - ); -}; - -export const ThreatDetailsView = React.memo(ThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx deleted file mode 100644 index fa12ff3db7895..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx +++ /dev/null @@ -1,43 +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 { ThreatSummaryView } from './threat_summary_view'; -import { TestProviders } from '../../mock'; -import { useMountAppended } from '../../utils/use_mount_appended'; -import { mockAlertDetailsData } from './__mocks__'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; - -jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { - return { - useRuleAsync: jest.fn(), - }; -}); - -const props = { - data: mockAlertDetailsData as TimelineEventsDetailsItem[], - eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', - timelineId: 'detections-page', -}; - -describe('ThreatSummaryView', () => { - const mount = useMountAppended(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('render correct items', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="threat-summary-view"]').exists()).toEqual(true); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx deleted file mode 100644 index 67b09e8e59699..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx +++ /dev/null @@ -1,78 +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 { EuiBasicTableColumn, EuiSpacer } from '@elastic/eui'; -import React from 'react'; - -import * as i18n from './translations'; -import { SummaryView } from './summary_view'; -import { getSummaryColumns, SummaryRow, ThreatSummaryRow } from './helpers'; -import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; -import { SORTED_THREAT_SUMMARY_FIELDS } from '../../../../common/cti/constants'; -import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants'; - -const getThreatSummaryRows = ( - data: TimelineEventsDetailsItem[], - timelineId: string, - eventId: string -) => - SORTED_THREAT_SUMMARY_FIELDS.map((threatSummaryField) => { - const item = data.find(({ field }) => field === threatSummaryField); - if (item) { - const { field, originalValue } = item; - return { - title: field.replace(`${INDICATOR_DESTINATION_PATH}.`, ''), - description: { - values: Array.isArray(originalValue) ? originalValue : [originalValue], - contextId: timelineId, - eventId, - fieldName: field, - }, - }; - } - return null; - }).filter((item: ThreatSummaryRow | null): item is ThreatSummaryRow => !!item); - -const getDescription = ({ - contextId, - eventId, - fieldName, - values, -}: ThreatSummaryRow['description']): JSX.Element => ( - <> - {values.map((value: string) => ( - - ))} - -); - -const summaryColumns: Array> = getSummaryColumns(getDescription); - -const ThreatSummaryViewComponent: React.FC<{ - data: TimelineEventsDetailsItem[]; - timelineId: string; - eventId: string; -}> = ({ data, timelineId, eventId }) => ( - <> - - - -); - -export const ThreatSummaryView = React.memo(ThreatSummaryViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index a28d1976ca940..a17ca5e434ace 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -23,23 +23,8 @@ export const THREAT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetail defaultMessage: 'Threat Summary', }); -export const NO_ENRICHMENT_FOUND = i18n.translate( - 'xpack.securitySolution.alertDetails.noEnrichmentFound', - { - defaultMessage: 'No Threat Intel Enrichment Found', - } -); - -export const IF_CTI_NOT_ENABLED = i18n.translate( - 'xpack.securitySolution.alertDetails.ifCtiNotEnabled', - { - defaultMessage: - "If you haven't enabled any threat intelligence sources and want to learn more about this capability, ", - } -); - -export const CHECK_DOCS = i18n.translate('xpack.securitySolution.alertDetails.checkDocs', { - defaultMessage: 'please check out our documentation.', +export const VIEW_CTI_DATA = i18n.translate('xpack.securitySolution.alertDetails.threatIntelCta', { + defaultMessage: 'View threat intel data', }); export const INVESTIGATION_GUIDE = i18n.translate( diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts new file mode 100644 index 0000000000000..179b4a53e3676 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts @@ -0,0 +1,54 @@ +/* + * 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 { Observable } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { + isErrorResponse, + isCompleteResponse, +} from '../../../../../../../../src/plugins/data/common'; +import { + CtiEventEnrichmentRequestOptions, + CtiEventEnrichmentStrategyResponse, + CtiQueries, +} from '../../../../../common/search_strategy/security_solution/cti'; + +type GetEventEnrichmentProps = CtiEventEnrichmentRequestOptions & { + data: DataPublicPluginStart; + signal: AbortSignal; +}; + +export const getEventEnrichment = ({ + data, + defaultIndex, + eventFields, + filterQuery, + timerange, + signal, +}: GetEventEnrichmentProps): Observable => + data.search.search( + { + defaultIndex, + eventFields, + factoryQueryType: CtiQueries.eventEnrichment, + filterQuery, + timerange, + }, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: signal, + } + ); + +export const getEventEnrichmentComplete = ( + props: GetEventEnrichmentProps +): Observable => + getEventEnrichment(props).pipe( + filter((response) => isErrorResponse(response) || isCompleteResponse(response)) + ); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts new file mode 100644 index 0000000000000..e8fb1a03045d9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './use_event_enrichment'; +export * from './use_investigation_enrichment'; diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts new file mode 100644 index 0000000000000..ff9130b288fa8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.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 { i18n } from '@kbn/i18n'; + +export const INVESTIGATION_ENRICHMENT_REQUEST_ERROR = i18n.translate( + 'xpack.securitySolution.investigationEnrichment.requestError', + { + defaultMessage: `An error occurred while requesting threat intelligence`, + } +); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts new file mode 100644 index 0000000000000..939566d6e59c3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts @@ -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 { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; + +import { getEventEnrichment, getEventEnrichmentComplete } from './api'; + +const getEventEnrichmentOptionalSignal = withOptionalSignal(getEventEnrichment); + +export const useEventEnrichment = () => useObservable(getEventEnrichmentOptionalSignal); + +const getEventEnrichmentCompleteWithOptionalSignal = withOptionalSignal(getEventEnrichmentComplete); + +export const useEventEnrichmentComplete = () => + useObservable(getEventEnrichmentCompleteWithOptionalSignal); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts new file mode 100644 index 0000000000000..c15b49fe5c41e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts @@ -0,0 +1,83 @@ +/* + * 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, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { isEmpty } from 'lodash'; + +import { EventFields } from '../../../../../common/search_strategy/security_solution/cti'; +import { + DEFAULT_CTI_SOURCE_INDEX, + DEFAULT_EVENT_ENRICHMENT_FROM, + DEFAULT_EVENT_ENRICHMENT_TO, +} from '../../../../../common/cti/constants'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { useKibana } from '../../../lib/kibana'; +import { inputsActions } from '../../../store/actions'; +import * as i18n from './translations'; +import { useEventEnrichmentComplete } from '.'; + +export const QUERY_ID = 'investigation_time_enrichment'; +const noop = () => {}; + +export const useInvestigationTimeEnrichment = (eventFields: EventFields) => { + const { addError } = useAppToasts(); + const kibana = useKibana(); + const dispatch = useDispatch(); + const [{ from, to }, setRange] = useState({ + from: DEFAULT_EVENT_ENRICHMENT_FROM, + to: DEFAULT_EVENT_ENRICHMENT_TO, + }); + const { error, loading, result, start } = useEventEnrichmentComplete(); + + const deleteQuery = useCallback(() => { + dispatch(inputsActions.deleteOneQuery({ inputId: 'global', id: QUERY_ID })); + }, [dispatch]); + + useEffect(() => { + if (!loading && result) { + dispatch( + inputsActions.setQuery({ + inputId: 'global', + id: QUERY_ID, + inspect: { + dsl: result.inspect.dsl, + response: [JSON.stringify(result.rawResponse, null, 2)], + }, + loading, + refetch: noop, + }) + ); + } + + return deleteQuery; + }, [deleteQuery, dispatch, loading, result]); + + useEffect(() => { + if (error) { + addError(error, { title: i18n.INVESTIGATION_ENRICHMENT_REQUEST_ERROR }); + } + }, [addError, error]); + + useEffect(() => { + if (!isEmpty(eventFields)) { + start({ + data: kibana.services.data, + timerange: { from, to, interval: '' }, + defaultIndex: DEFAULT_CTI_SOURCE_INDEX, + eventFields, + filterQuery: '', + }); + } + }, [from, start, kibana.services.data, to, eventFields]); + + return { + loading, + result, + setRange, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts index 7c80572f6b1ee..4a51880e0f227 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts @@ -7,6 +7,7 @@ import { get } from 'lodash'; import { INDICATOR_DESTINATION_PATH } from '../../../../../common/constants'; +import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; import { getThreatListItemMock } from './build_threat_mapping_filter.mock'; import { @@ -158,14 +159,14 @@ describe('buildMatchedIndicator', () => { expect(get(indicator, 'matched.field')).toEqual('event.field'); }); - it('returns the type of the matched indicator as matched.type', () => { + it('returns the type of the enrichment as an indicator match type', () => { const [indicator] = buildMatchedIndicator({ queries, threats, indicatorPath, }); - expect(get(indicator, 'matched.type')).toEqual('type_1'); + expect(get(indicator, 'matched.type')).toEqual(ENRICHMENT_TYPES.IndicatorMatchRule); }); it('returns indicators for each provided query', () => { @@ -216,7 +217,7 @@ describe('buildMatchedIndicator', () => { id: '123', index: 'threat-index', field: 'event.field', - type: 'type_1', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, other: 'other_1', type: 'type_1', @@ -263,7 +264,7 @@ describe('buildMatchedIndicator', () => { id: '123', index: 'threat-index', field: 'event.field', - type: 'indicator_type', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, type: 'indicator_type', event: { @@ -294,7 +295,7 @@ describe('buildMatchedIndicator', () => { id: '123', index: 'threat-index', field: 'event.field', - type: undefined, + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, }, ]); @@ -321,7 +322,7 @@ describe('buildMatchedIndicator', () => { id: '123', index: 'threat-index', field: 'event.field', - type: undefined, + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, }, ]); @@ -359,7 +360,7 @@ describe('buildMatchedIndicator', () => { id: '123', index: 'threat-index', field: 'event.field', - type: 'first', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, type: 'first', event: { @@ -478,7 +479,7 @@ describe('enrichSignalThreatMatches', () => { id: '123', index: 'indicator_index', field: 'event.field', - type: 'type_1', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, other: 'other_1', type: 'type_1', @@ -510,7 +511,7 @@ describe('enrichSignalThreatMatches', () => { id: '123', index: 'indicator_index', field: 'event.field', - type: undefined, + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, }, ]); @@ -543,7 +544,7 @@ describe('enrichSignalThreatMatches', () => { id: '123', index: 'indicator_index', field: 'event.field', - type: 'type_1', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, other: 'other_1', type: 'type_1', @@ -608,7 +609,7 @@ describe('enrichSignalThreatMatches', () => { id: '123', index: 'custom_index', field: 'event.field', - type: 'custom_type', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, other: 'custom_other', type: 'custom_type', @@ -670,7 +671,7 @@ describe('enrichSignalThreatMatches', () => { id: '123', index: 'indicator_index', field: 'event.field', - type: 'type_1', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, event: { category: 'threat', @@ -685,7 +686,7 @@ describe('enrichSignalThreatMatches', () => { id: '456', index: 'other_custom_index', field: 'event.other', - type: 'type_2', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, event: { category: 'bad', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts index c26f03d1dd480..3423cc1a8744f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts @@ -6,6 +6,7 @@ */ import { get, isObject } from 'lodash'; +import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; import type { SignalSearchResponse, SignalSourceHit } from '../types'; import type { @@ -56,13 +57,18 @@ export const buildMatchedIndicator = ({ throw new Error(`Expected indicator field to be an object, but found: ${indicator}`); } const atomic = get(matchedThreat?._source, query.value) as unknown; - const type = get(indicator, 'type') as unknown; const event = get(matchedThreat?._source, 'event') as unknown; return { ...indicator, event, - matched: { atomic, field: query.field, id: query.id, index: query.index, type }, + matched: { + atomic, + field: query.field, + id: query.id, + index: query.index, + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, }; }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts index e4ed05baeed77..f24bfc08b39e0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts @@ -8,14 +8,16 @@ import { get, isEmpty } from 'lodash'; import { estypes } from '@elastic/elasticsearch'; -import { EVENT_ENRICHMENT_INDICATOR_FIELD_MAP } from '../../../../../../common/cti/constants'; -import { CtiEnrichment } from '../../../../../../common/search_strategy/security_solution/cti'; - -type EventField = keyof typeof EVENT_ENRICHMENT_INDICATOR_FIELD_MAP; -const validEventFields = Object.keys(EVENT_ENRICHMENT_INDICATOR_FIELD_MAP) as EventField[]; - -const isValidEventField = (field: string): field is EventField => - validEventFields.includes(field as EventField); +import { + ENRICHMENT_TYPES, + EVENT_ENRICHMENT_INDICATOR_FIELD_MAP, +} from '../../../../../../common/cti/constants'; +import { + CtiEnrichment, + EventField, + isValidEventField, + validEventFields, +} from '../../../../../../common/search_strategy/security_solution/cti'; export const buildIndicatorShouldClauses = ( eventFields: Record @@ -67,6 +69,7 @@ const buildIndicatorMatchedFields = ( 'matched.field': [eventField], 'matched.id': [hit._id], 'matched.index': [hit._index], + 'matched.type': [ENRICHMENT_TYPES.InvestigationTime], }; }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index e6a835462619c..c64713575c130 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -28,6 +28,7 @@ import { import { getCreateThreatMatchRulesSchemaMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock'; import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks'; import { SIGNALS_TEMPLATE_VERSION } from '../../../../plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template'; +import { ENRICHMENT_TYPES } from '../../../../plugins/security_solution/common/cti/constants'; const format = (value: unknown): string => JSON.stringify(value, null, 2); @@ -425,7 +426,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978783', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, provider: 'geenensp', type: 'url', @@ -457,7 +458,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978783', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, provider: 'geenensp', type: 'url', @@ -519,7 +520,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978785', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, port: 57324, provider: 'geenensp', @@ -544,7 +545,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978787', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', - type: 'ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, provider: 'other_provider', type: 'ip', @@ -619,7 +620,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978785', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, port: 57324, provider: 'geenensp', @@ -649,7 +650,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978785', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.port', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, port: 57324, provider: 'geenensp', @@ -674,7 +675,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978787', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', - type: 'ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, provider: 'other_provider', type: 'ip', @@ -754,7 +755,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978783', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, provider: 'geenensp', type: 'url', @@ -785,7 +786,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978783', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'destination.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, provider: 'geenensp', type: 'url', @@ -813,7 +814,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978785', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.ip', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, port: 57324, provider: 'geenensp', @@ -838,7 +839,7 @@ export default ({ getService }: FtrProviderContext) => { id: '978785', index: 'filebeat-8.0.0-2021.01.26-000001', field: 'source.port', - type: 'url', + type: ENRICHMENT_TYPES.IndicatorMatchRule, }, port: 57324, provider: 'geenensp', diff --git a/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json b/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json index 11b5e9bd0828b..543250ba17499 100644 --- a/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json @@ -7,6 +7,9 @@ "@timestamp": "2021-02-22T21:00:49.337Z", "myhash": { "mysha256": "a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3" + }, + "source": { + "ip": "192.168.1.1" } } } diff --git a/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json b/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json index 9573372d02e9c..c5d382194027f 100644 --- a/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json @@ -47,7 +47,6 @@ "input": { "type": "httpjson" }, - "@timestamp": "2021-03-10T14:51:07.663Z", "ecs": { "version": "1.6.0" }, diff --git a/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json new file mode 100644 index 0000000000000..0598fd7ba7c86 --- /dev/null +++ b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json @@ -0,0 +1,63 @@ +{ + "type": "doc", + "value": { + "id": "a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3", + "index": "filebeat-7.12.0-2021.03.11-000001", + "source": { + "@timestamp": "2021-06-27T14:51:05.766Z", + "agent": { + "ephemeral_id": "34c78500-8df5-4a07-ba87-1cc738b98431", + "hostname": "test", + "id": "08a3d064-8f23-41f3-84b2-f917f6ff9588", + "name": "test", + "type": "filebeat", + "version": "7.12.0" + }, + "fileset": { + "name": "abusemalware" + }, + "threatintel": { + "indicator": { + "first_seen": "2021-03-11T08:02:14.000Z", + "ip": "192.168.1.1", + "provider": "another_provider", + "type": "ip" + }, + "abusemalware": { + "virustotal": { + "result": "38 / 61", + "link": "https://www.virustotal.com/gui/file/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/detection/f-a04ac6d", + "percent": "62.30" + } + } + }, + "tags": ["threatintel-abusemalware", "forwarded"], + "input": { + "type": "httpjson" + }, + "ecs": { + "version": "1.6.0" + }, + "related": { + "hash": [ + "9b6c3518a91d23ed77504b5416bfb5b3", + "a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3", + "1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL" + ] + }, + "service": { + "type": "threatintel" + }, + "event": { + "reference": "https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/", + "ingested": "2021-03-11T14:51:09.809069Z", + "created": "2021-03-11T14:51:07.663Z", + "kind": "enrichment", + "module": "threatintel", + "category": "threat", + "type": "indicator", + "dataset": "threatintel.abusemalware" + } + } + } +} diff --git a/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json new file mode 100644 index 0000000000000..072318f7f4fc4 --- /dev/null +++ b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json @@ -0,0 +1,822 @@ +{ + "type": "index", + "value": { + "aliases": { + "filebeat-7.12.0": { + "is_write_index": false + } + }, + "index": "filebeat-7.12.0-2021.03.11-000001", + "mappings": { + "_meta": { + "beat": "filebeat", + "version": "7.12.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "kubernetes.service.selectors.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.service.selectors.*" + } + }, + { + "docker.attrs": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.attrs.*" + } + }, + { + "azure.activitylogs.identity.claims.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "azure.activitylogs.identity.claims.*" + } + }, + { + "azure.platformlogs.properties.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "azure.platformlogs.properties.*" + } + }, + { + "kibana.log.meta": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "kibana.log.meta.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "build": { + "properties": { + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "apache": { + "properties": { + "access": { + "properties": { + "ssl": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "fileset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "threatintel": { + "properties": { + "abusemalware": { + "properties": { + "file_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature": { + "ignore_above": 1024, + "type": "keyword" + }, + "urlhaus_download": { + "ignore_above": 1024, + "type": "keyword" + }, + "virustotal": { + "properties": { + "link": { + "ignore_above": 1024, + "type": "keyword" + }, + "percent": { + "type": "float" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "abuseurl": { + "properties": { + "blacklists": { + "properties": { + "spamhaus_dbl": { + "ignore_above": 1024, + "type": "keyword" + }, + "surbl": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "larted": { + "type": "boolean" + }, + "reporter": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "ignore_above": 1024, + "type": "keyword" + }, + "url_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "urlhaus_reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "anomali": { + "properties": { + "content": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "indicator": { + "ignore_above": 1024, + "type": "keyword" + }, + "labels": { + "ignore_above": 1024, + "type": "keyword" + }, + "modified": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "object_marking_refs": { + "ignore_above": 1024, + "type": "keyword" + }, + "pattern": { + "ignore_above": 1024, + "type": "keyword" + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "valid_from": { + "type": "date" + } + } + }, + "indicator": { + "properties": { + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "confidence": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssdeep": { + "ignore_above": 1024, + "type": "keyword" + }, + "tlsh": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "imphash": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "first_seen": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ip": { + "type": "ip" + }, + "last_seen": { + "type": "date" + }, + "marking": { + "properties": { + "tlp": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "matched": { + "properties": { + "atomic": { + "ignore_above": 1024, + "type": "keyword" + }, + "field": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "registry": { + "properties": { + "data": { + "properties": { + "strings": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "scanner_stats": { + "type": "long" + }, + "sightings": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "x509": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "misp": { + "properties": { + "attribute": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "comment": { + "ignore_above": 1024, + "type": "keyword" + }, + "deleted": { + "type": "boolean" + }, + "disable_correlation": { + "type": "boolean" + }, + "distribution": { + "type": "long" + }, + "event_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "object_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "object_relation": { + "ignore_above": 1024, + "type": "keyword" + }, + "sharing_group_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "to_ids": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "attribute_count": { + "type": "long" + }, + "date": { + "type": "date" + }, + "disable_correlation": { + "type": "boolean" + }, + "distribution": { + "ignore_above": 1024, + "type": "keyword" + }, + "extends_uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "info": { + "ignore_above": 1024, + "type": "keyword" + }, + "locked": { + "type": "boolean" + }, + "org": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "local": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "org_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "orgc": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "local": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "orgc_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "proposal_email_lock": { + "type": "boolean" + }, + "publish_timestamp": { + "type": "date" + }, + "published": { + "type": "boolean" + }, + "sharing_group_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat_level_id": { + "type": "long" + }, + "timestamp": { + "type": "date" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "otx": { + "properties": { + "content": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "indicator": { + "ignore_above": 1024, + "type": "keyword" + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": "filebeat", + "rollover_alias": "filebeat-7.12.0" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "max_docvalue_fields_search": "200", + "number_of_replicas": "1", + "number_of_shards": "1", + "refresh_interval": "5s" + } + } + } +}