diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts index df9a4fbce88ff..ede5335dfd05e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { get, isEqual } from 'lodash'; +import { get, isEqual, omit } from 'lodash'; import expect from '@kbn/expect'; import { ALERT_REASON, @@ -19,6 +19,7 @@ import { VERSION, } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; +import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; @@ -32,6 +33,7 @@ import { ALERT_ORIGINAL_EVENT_MODULE, ALERT_ORIGINAL_TIME, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; import { previewRule, getOpenSignals, @@ -41,7 +43,6 @@ import { createRule, } from '../../utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; - const format = (value: unknown): string => JSON.stringify(value, null, 2); // Asserts that each expected value is included in the subject, independent of @@ -54,6 +55,85 @@ const assertContains = (subject: unknown[], expected: unknown[]) => ) ); +const createThreatMatchRule = ({ + name = 'Query with a rule id', + index = ['auditbeat-*'], + query = '*:*', + // eslint-disable-next-line @typescript-eslint/naming-convention + rule_id = 'rule-1', + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_indicator_path = 'threat.indicator', + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_query = 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_index = ['auditbeat-*'], + // eslint-disable-next-line @typescript-eslint/naming-convention + threat_mapping = [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + override = {}, +}: { + threat_mapping?: ThreatMapping; + name?: string; + query?: string; + rule_id?: string; + index?: string[]; + threat_index?: string[]; + threat_query?: string; + threat_indicator_path?: string; + override?: any; +} = {}): ThreatMatchRuleCreateProps => ({ + description: 'Detecting root and admin users', + name, + severity: 'high', + index, + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id, + from: '1900-01-01T00:00:00.000Z', + query, + threat_query, + threat_index, + threat_mapping, + threat_filters: [], + threat_indicator_path, + ...override, +}); + +function alertsAreTheSame(alertsA: any[], alertsB: any[]): void { + const mapAlert = (alert: any) => { + return omit(alert._source, [ + '@timestamp', + 'kibana.alert.last_detected', + 'kibana.rule.created_at', + 'kibana.rule.execution.uuid', + 'kibana.name.execution.uuid', + 'kibana.alert.rule.parameters', + 'kibana.alert.rule.rule_id', + 'kibana.alert.rule.name', + 'kibana.alert.rule.created_at', + 'kibana.alert.rule.updated_at', + 'kibana.alert.rule.uuid', + 'kibana.alert.rule.execution.uuid', + 'kibana.alert.start', + 'kibana.alert.reason', + 'kibana.alert.uuid', + ]); + }; + + expect(alertsA.map(mapAlert)).to.eql(alertsB.map(mapAlert)); +} + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); @@ -66,6 +146,8 @@ export default ({ getService }: FtrProviderContext) => { */ describe('Threat match type rules', () => { before(async () => { + // await deleteSignalsIndex(supertest, log); + // await deleteAllAlerts(supertest, log); await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); @@ -76,38 +158,19 @@ export default ({ getService }: FtrProviderContext) => { }); // First 2 test creates a real rule - remaining tests use preview API - it('should be able to execute and get 10 signals when doing a specific query (terms query)', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; + it('should be able to execute and get all signals when doing a specific query (terms query)', async () => { + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule(); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenSignals(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).equal(10); + const alerts = await getOpenSignals( + supertest, + log, + es, + createdRule, + RuleExecutionStatus.succeeded, + 100 + ); + expect(alerts.hits.hits.length).equal(88); const fullSource = alerts.hits.hits.find( (signal) => (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' @@ -257,20 +320,8 @@ export default ({ getService }: FtrProviderContext) => { }), }); }); - it('should be able to execute and get 10 signals when doing a specific query (match query)', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + it('should be able to execute and get all signals when doing a specific query (match query)', async () => { + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ // We match host.name against host.name { @@ -288,12 +339,18 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenSignals(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).equal(10); + const alerts = await getOpenSignals( + supertest, + log, + es, + createdRule, + RuleExecutionStatus.succeeded, + 100 + ); + expect(alerts.hits.hits.length).equal(88); const fullSource = alerts.hits.hits.find( (signal) => (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' @@ -444,20 +501,64 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('terms and match should have the same alerts with pagination', async () => { + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + items_per_search: 1, + concurrent_searches: 1, + }, + }); + + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + items_per_search: 1, + concurrent_searches: 1, + }, + name: 'Math rule', + rule_id: 'rule-2', + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + }); + + const createdRuleTerm = await createRule(supertest, log, termRule); + const createdRuleMatch = await createRule(supertest, log, matchRule); + const alertsTerm = await getOpenSignals( + supertest, + log, + es, + createdRuleTerm, + RuleExecutionStatus.succeeded, + 100 + ); + const alertsMatch = await getOpenSignals( + supertest, + log, + es, + createdRuleMatch, + RuleExecutionStatus.succeeded, + 100 + ); + + alertsAreTheSame(alertsTerm.hits.hits, alertsMatch.hits.hits); + }); + it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ // We match host.name against host.name { @@ -470,8 +571,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -479,19 +579,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -508,8 +596,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -517,19 +604,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - type: 'threat_match', - index: ['auditbeat-*'], - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -546,8 +621,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -557,35 +631,13 @@ export default ({ getService }: FtrProviderContext) => { describe('timeout behavior', () => { // TODO: unskip this and see if we can make it not flaky it.skip('will return an error if a rule execution exceeds the rule interval', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a short interval', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: '*:*', // broad query to take more time - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - concurrent_searches: 1, - interval: '1s', // short interval - items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow - }; + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + override: { + concurrent_searches: 1, + interval: '1s', // short interval + items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow + }, + }); const { logs } = await previewRule({ supertest, rule }); expect(logs[0].errors[0]).to.contain('execution has exceeded its allotted interval'); @@ -602,20 +654,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with the single indicator that matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow events down to 2 with a destination.ip - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_mapping: [ { entries: [ @@ -627,8 +666,9 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -690,18 +730,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with multiple indicators if several matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -715,9 +745,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; - + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); expect(previewAlerts.length).equal(1); @@ -767,18 +795,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('adds a single indicator that matched multiple fields', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -801,8 +819,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -876,19 +893,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates multiple signals with multiple matches', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ threat_query: '*:*', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -916,8 +921,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -1023,18 +1027,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('enriches signals with the single indicator that matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'destination.ip:159.89.119.67', - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1048,14 +1042,36 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'destination.ip:159.89.119.67', + threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(2); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(2); - const threats = previewAlerts.map((hit) => hit._source?.threat); + const threats = termPreviewAlerts.map((hit) => hit._source?.threat); expect(threats).to.eql([ { enrichments: [ @@ -1108,21 +1124,12 @@ export default ({ getService }: FtrProviderContext) => { ], }, ]); + alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts); }); it('enriches signals with multiple indicators if several matched', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1136,14 +1143,36 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(1); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(1); - const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + const [threat] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{ enrichments: unknown[]; }>; @@ -1185,21 +1214,12 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]); + alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts); }); it('adds a single indicator that matched multiple fields', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', threat_query: 'threat.indicator.ip: *', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1222,18 +1242,57 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); + const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({ + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + }); - const { previewId } = await previewRule({ supertest, rule }); - const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).equal(1); + const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule }); + const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId }); + const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule }); + const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId }); + expect(termPreviewAlerts.length).equal(1); - const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + const [threatTerm] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{ enrichments: unknown[]; }>; - assertContains(threat.enrichments, [ + const [threatMatch] = matchPrevieAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + assertContains(threatTerm.enrichments, [ { feed: {}, indicator: { @@ -1294,22 +1353,23 @@ export default ({ getService }: FtrProviderContext) => { }, }, ]); + const sortEnrichments = (a: any, b: any) => { + const atomicA = a.matched.atomic.toString(); + const atomicB = b.matched.atomic.toString(); + if (atomicA === atomicB) { + return a.indicator.description > b.indicator.description ? 1 : -1; + } + return atomicA > atomicB ? 1 : -1; + }; + + expect(threatTerm.enrichments.sort(sortEnrichments)).to.be.eql( + threatMatch.enrichments.sort(sortEnrichments) + ); }); it('generates multiple signals with multiple matches', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', threat_query: '*:*', threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module threat_mapping: [ @@ -1337,8 +1397,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); @@ -1444,19 +1503,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be enriched with host risk score', async () => { - const rule: ThreatMatchRuleCreateProps = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', + const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({ query: '*:*', threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity threat_mapping: [ // We match host.name against host.name { @@ -1469,8 +1518,7 @@ export default ({ getService }: FtrProviderContext) => { ], }, ], - threat_filters: [], - }; + }); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 }); diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 476b6223b9591..7df4bf6ee727f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -116,5 +116,6 @@ "@kbn/cloud-integration-saml-provider-plugin", "@kbn/security-api-integration-helpers", "@kbn/discover-plugin", + "@kbn/securitysolution-io-ts-alerting-types", ] }