import { KbnClient } from '@kbn/test' ; import { ALERT_RULE_RULE_ID , ALERT_RULE_UUID } from '@kbn/rule-data-utils' ; import type { TransportResult } from '@elastic/elasticsearch' ; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey' ; import type { Client } from '@elastic/elasticsearch' ; import type SuperTest from 'supertest' ; import type { ListArray , NonEmptyEntriesArray , OsTypeArray , } from '@kbn/securitysolution-io-ts-list-types' ; import type { CreateExceptionListItemSchema , CreateExceptionListSchema , ExceptionListItemSchema , ExceptionListSchema , } from '@kbn/securitysolution-io-ts-list-types' ; import { EXCEPTION_LIST_ITEM_URL , EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants' ; import { ToolingLog } from '@kbn/dev-utils' ; import { PrePackagedRulesAndTimelinesStatusSchema } from '../../plugins/security_solution/common/detection_engine/schemas/response' ; import { CreateRulesSchema , UpdateRulesSchema , FullResponseSchema , QueryCreateSchema , EqlCreateSchema , ThresholdCreateSchema , PreviewRulesSchema , ThreatMatchCreateSchema , RulePreviewLogs , } from '../../plugins/security_solution/common/detection_engine/schemas/request' ; import { signalsMigrationType } from '../../plugins/security_solution/server/lib/detection_engine/migrations/saved_objects' ; import { Status , SignalIds , } from '../../plugins/security_solution/common/detection_engine/schemas/common/schemas' ; import { RulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema' ; import { DETECTION_ENGINE_INDEX_URL , DETECTION_ENGINE_PREPACKAGED_URL , DETECTION_ENGINE_QUERY_SIGNALS_URL , DETECTION_ENGINE_RULES_URL , DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL , DETECTION_ENGINE_SIGNALS_MIGRATION_URL , INTERNAL_IMMUTABLE_KEY , INTERNAL_RULE_ID_KEY , UPDATE_OR_CREATE_LEGACY_ACTIONS , } from '../../plugins/security_solution/common/constants' ; import { RACAlert } from '../../plugins/security_solution/server/lib/detection_engine/rule_types/types' ; import { DetectionMetrics } from '../../plugins/security_solution/server/usage/detections/types' ; param rule export const removeServerGeneratedProperties = ( rule ) : Partial < FullResponseSchema > => { const { created_at , updated_at , id , last_failure_at , last_failure_message , last_success_at , last_success_message , last_gap , search_after_time_durations , bulk_create_time_durations , status , status_date , ... removedProperties } = rule ; return removedProperties ; } ; param rule export const removeServerGeneratedPropertiesIncludingRuleId = ( rule ) : Partial < FullResponseSchema > => { const ruleWithRemovedProperties = removeServerGeneratedProperties ( rule ) ; const { rule_id , ... additionalRuledIdRemoved } = ruleWithRemovedProperties ; return additionalRuledIdRemoved ; } ; param ruleId param enabled export const getSimpleRule = ( ruleId = 'rule-1' , enabled = false ) : QueryCreateSchema => ( { name : 'Simple Rule Query' , description : 'Simple Rule Query' , enabled , risk_score : 1 , rule_id : ruleId , severity : 'high' , index : [ 'auditbeat-*' ] , type : 'query' , query : 'user.name: root or user.name: admin' , } ) ; param ruleId param enabled export const getSimplePreviewRule = ( ruleId = 'preview-rule-1' , invocationCount = 20 ) : PreviewRulesSchema => ( { name : 'Simple Rule Query' , description : 'Simple Rule Query' , risk_score : 1 , rule_id : ruleId , severity : 'high' , index : [ 'auditbeat-*' ] , type : 'query' , query : 'user.name: root or user.name: admin' , invocationCount , } ) ; param ruleId param enabled export const getRuleForSignalTesting = ( index , ruleId = 'rule-1' , enabled = true ) : QueryCreateSchema => ( { name : 'Signal Testing Query' , description : 'Tests a simple query' , enabled , risk_score : 1 , rule_id : ruleId , severity : 'high' , index , type : 'query' , query : '*:*' , from : '1900-01-01T00:00:00.000Z' , } ) ; param ruleId param enabled export const getEqlRuleForSignalTesting = ( index , ruleId = 'eql-rule' , enabled = true ) : EqlCreateSchema => ( { ... getRuleForSignalTesting ( index , ruleId , enabled ) , type : 'eql' , language : 'eql' , query : 'any where true' , } ) ; param ruleId param enabled export const getThresholdRuleForSignalTesting = ( index , ruleId = 'threshold-rule' , enabled = true ) : ThresholdCreateSchema => ( { ... getRuleForSignalTesting ( index , ruleId , enabled ) , type : 'threshold' , language : 'kuery' , query : '*:*' , threshold : { field : 'process.name' , value : 21 , } , } ) ; export const getRuleForSignalTestingWithTimestampOverride = ( index , ruleId = 'rule-1' , enabled = true , timestampOverride = 'event.ingested' ) : QueryCreateSchema => ( { name : 'Signal Testing Query' , description : 'Tests a simple query' , enabled , risk_score : 1 , rule_id : ruleId , severity : 'high' , index , type : 'query' , query : '*:*' , timestamp_override : timestampOverride , from : '1900-01-01T00:00:00.000Z' , } ) ; param ruleId param enabled export const getSimpleRuleUpdate = ( ruleId = 'rule-1' , enabled = false ) : UpdateRulesSchema => ( { name : 'Simple Rule Query' , description : 'Simple Rule Query' , enabled , risk_score : 1 , rule_id : ruleId , severity : 'high' , index : [ 'auditbeat-*' ] , type : 'query' , query : 'user.name: root or user.name: admin' , } ) ; param ruleId param enabled export const getSimpleMlRule = ( ruleId = 'rule-1' , enabled = false ) : CreateRulesSchema => ( { name : 'Simple ML Rule' , description : 'Simple Machine Learning Rule' , enabled , anomaly_threshold : 44 , risk_score : 1 , rule_id : ruleId , severity : 'high' , machine_learning_job_id : [ 'some_job_id' ] , type : 'machine_learning' , } ) ; param ruleId param enabled export const getSimpleMlRuleUpdate = ( ruleId = 'rule-1' , enabled = false ) : UpdateRulesSchema => ( { name : 'Simple ML Rule' , description : 'Simple Machine Learning Rule' , enabled , anomaly_threshold : 44 , risk_score : 1 , rule_id : ruleId , severity : 'high' , machine_learning_job_id : [ 'some_job_id' ] , type : 'machine_learning' , } ) ; export const getSignalStatus = ( ) => ( { aggs : { statuses : { terms : { field : 'kibana.alert.workflow_status' , size : 10 } } } , } ) ; export const getQueryAllSignals = ( ) => ( { query : { match_all : { } } , } ) ; export const getQuerySignalIds = ( signalIds ) => ( { query : { terms : { _id : signalIds , } , } , } ) ; param ruleIds export const getQuerySignalsRuleId = ( ruleIds ) => ( { query : { terms : { [ ALERT_RULE_RULE_ID ] : ruleIds , } , } , } ) ; param ruleIds export const getQuerySignalsId = ( ids , size = 10 ) => ( { size , query : { terms : { [ ALERT_RULE_UUID ] : ids , } , } , } ) ; export const setSignalStatus = ( { signalIds , status , } ) => ( { signal_ids : signalIds , status , } ) ; export const getSignalStatusEmptyResponse = ( ) => ( { timed_out : false , total : 0 , updated : 0 , deleted : 0 , batches : 0 , version_conflicts : 0 , noops : 0 , retries : { bulk : 0 , search : 0 } , throttled_millis : 0 , requests_per_second : - 1 , throttled_until_millis : 0 , failures : [ ] , } ) ; /**
 * This is a typical simple rule for testing that is easy for most basic testing
 */ export const getSimpleRuleWithoutRuleId = ( ) : CreateRulesSchema => { const simpleRule = getSimpleRule ( ) ; const { rule_id , ... ruleWithoutId } = simpleRule ; return ruleWithoutId ; } ; param res param callback export const binaryToString = ( res , callback ) : void => { res . setEncoding ( 'binary' ) ; res . data = '' ; res . on ( 'data' , ( chunk ) => { res . data += chunk ; } ) ; res . on ( 'end' , ( ) => { callback ( null , Buffer . from ( res . data ) ) ; } ) ; } ; /**
 * This is the typical output of a simple rule that Kibana will output with all the defaults
 * except for the server generated properties.  Useful for testing end to end tests.
 */ export const getSimpleRuleOutput = ( ruleId = 'rule-1' , enabled = false ) : Partial < RulesSchema > => ( { actions : [ ] , author : [ ] , created_by : 'elastic' , description : 'Simple Rule Query' , enabled , false_positives : [ ] , from : 'now-6m' , immutable : false , index : [ 'auditbeat-*' ] , interval : '5m' , rule_id : ruleId , language : 'kuery' , output_index : '.siem-signals-default' , max_signals : 100 , risk_score : 1 , risk_score_mapping : [ ] , name : 'Simple Rule Query' , query : 'user.name: root or user.name: admin' , references : [ ] , severity : 'high' , severity_mapping : [ ] , updated_by : 'elastic' , tags : [ ] , to : 'now' , type : 'query' , threat : [ ] , throttle : 'no_actions' , exceptions_list : [ ] , version : 1 , } ) ; param previewId param logs export const getSimpleRulePreviewOutput = ( previewId = undefined , logs = [ ] ) => ( { logs , previewId , } ) ; export const resolveSimpleRuleOutput = ( ruleId = 'rule-1' , enabled = false ) : Partial < RulesSchema > => ( { outcome : 'exactMatch' , ... getSimpleRuleOutput ( ruleId , enabled ) } ) ; /**
 * This is the typical output of a simple rule that Kibana will output with all the defaults except
 * for all the server generated properties such as created_by. Useful for testing end to end tests.
 */ export const getSimpleRuleOutputWithoutRuleId = ( ruleId = 'rule-1' ) : Partial < RulesSchema > => { const rule = getSimpleRuleOutput ( ruleId ) ; const { rule_id : rId , ... ruleWithoutRuleId } = rule ; return ruleWithoutRuleId ; } ; /**
 * This is the typical output of a simple rule that Kibana will output with all the defaults except
 * for all the server generated properties such as created_by. Useful for testing end to end tests.
 */ export const resolveSimpleRuleOutputWithoutRuleId = ( ruleId = 'rule-1' ) : Partial < RulesSchema > => { const rule = getSimpleRuleOutput ( ruleId ) ; const { rule_id : rId , ... ruleWithoutRuleId } = rule ; return { outcome : 'exactMatch' , ... ruleWithoutRuleId } ; } ; export const getSimpleMlRuleOutput = ( ruleId = 'rule-1' ) : Partial < RulesSchema > => { const rule = getSimpleRuleOutput ( ruleId ) ; const { query , language , index , ... rest } = rule ; return { ... rest , name : 'Simple ML Rule' , description : 'Simple Machine Learning Rule' , anomaly_threshold : 44 , machine_learning_job_id : [ 'some_job_id' ] , type : 'machine_learning' , } ; } ; param supertest export const deleteAllAlerts = async ( supertest , log ) : Promise < void > => { await countDownTest ( async ( ) => { const { body } = await supertest . get ( `${ DETECTION_ENGINE_RULES_URL }/_find?per_page=9999` ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; const ids = body . data . map ( ( rule ) => ( { id : rule . id , } ) ) ; await supertest . post ( `${ DETECTION_ENGINE_RULES_URL }/_bulk_delete` ) . send ( ids ) . set ( 'kbn-xsrf' , 'true' ) ; const { body : finalCheck } = await supertest . get ( `${ DETECTION_ENGINE_RULES_URL }/_find` ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; return finalCheck . data . length === 0 ; } , 'deleteAllAlerts' , log , 50 , 1000 ) ; } ; export const downgradeImmutableRule = async ( es , log , ruleId ) : Promise < void > => { return countDownES ( async ( ) => { return es . updateByQuery ( { index : '.kibana' , refresh : true , wait_for_completion : true , body : { script : { lang : 'painless' , source : 'ctx._source.alert.params.version--' , } , query : { term : { 'alert.tags' : `${ INTERNAL_RULE_ID_KEY }:${ ruleId }` , } , } , } , } , { meta : true } ) ; } , 'downgradeImmutableRule' , log ) ; } ; param es export const deleteAllTimelines = async ( es ) : Promise < void > => { await es . deleteByQuery ( { index : '.kibana' , q : 'type:siem-ui-timeline' , wait_for_completion : true , refresh : true , body : { } , } ) ; } ; param es param log export const deleteAllRulesStatuses = async ( es , log ) : Promise < void > => { return countDownES ( async ( ) => { return es . deleteByQuery ( { index : '.kibana' , q : 'type:siem-detection-engine-rule-status' , wait_for_completion : true , refresh : true , body : { } , } , { meta : true } ) ; } , 'deleteAllRulesStatuses' , log ) ; } ; param supertest export const createSignalsIndex = async ( supertest , log ) : Promise < void > => { await countDownTest ( async ( ) => { await supertest . post ( DETECTION_ENGINE_INDEX_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; return true ; } , 'createSignalsIndex' , log ) ; } ; export const createLegacyRuleAction = async ( supertest , alertId , connectorId ) : Promise < unknown > => supertest . post ( `${ UPDATE_OR_CREATE_LEGACY_ACTIONS }` ) . set ( 'kbn-xsrf' , 'true' ) . query ( { alert_id : alertId } ) . send ( { name : 'Legacy notification with one action' , interval : '1m' , actions : [ { id : connectorId , group : 'default' , params : { message : 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts' , } , actionTypeId : '.slack' , } , ] , } ) ; param supertest export const deleteSignalsIndex = async ( supertest , log ) : Promise < void > => { await countDownTest ( async ( ) => { await supertest . delete ( DETECTION_ENGINE_INDEX_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; return true ; } , 'deleteSignalsIndex' , log ) ; } ; param ruleIds export const getSimpleRuleAsNdjson = ( ruleIds , enabled = false ) : Buffer => { const stringOfRules = ruleIds . map ( ( ruleId ) => { const simpleRule = getSimpleRule ( ruleId , enabled ) ; return JSON . stringify ( simpleRule ) ; } ) ; return Buffer . from ( stringOfRules . join ( '\n' ) ) ; } ; param rule export const ruleToNdjson = ( rule ) : Buffer => { const stringified = JSON . stringify ( rule ) ; return Buffer . from ( `${ stringified }\n` ) ; } ; param ruleId export const getComplexRule = ( ruleId = 'rule-1' ) : Partial < RulesSchema > => ( { actions : [ ] , author : [ ] , name : 'Complex Rule Query' , description : 'Complex Rule Query' , false_positives : [ 'https://www.example.com/some-article-about-a-false-positive' , 'some text string about why another condition could be a false positive' , ] , risk_score : 1 , risk_score_mapping : [ ] , rule_id : ruleId , filters : [ { query : { match_phrase : { 'host.name' : 'siem-windows' , } , } , } , ] , enabled : false , index : [ 'auditbeat-*' , 'filebeat-*' ] , interval : '5m' , output_index : '.siem-signals-default' , meta : { anything_you_want_ui_related_or_otherwise : { as_deep_structured_as_you_need : { any_data_type : { } , } , } , } , max_signals : 10 , tags : [ 'tag 1' , 'tag 2' , 'any tag you want' ] , to : 'now' , from : 'now-6m' , severity : 'high' , severity_mapping : [ ] , language : 'kuery' , type : 'query' , threat : [ { framework : 'MITRE ATT&CK' , tactic : { id : 'TA0040' , name : 'impact' , reference : 'https://attack.mitre.org/tactics/TA0040/' , } , technique : [ { id : 'T1499' , name : 'endpoint denial of service' , reference : 'https://attack.mitre.org/techniques/T1499/' , } , ] , } , { framework : 'Some other Framework you want' , tactic : { id : 'some-other-id' , name : 'Some other name' , reference : 'https://example.com' , } , technique : [ { id : 'some-other-id' , name : 'some other technique name' , reference : 'https://example.com' , } , ] , } , ] , references : [ 'http://www.example.com/some-article-about-attack' , 'Some plain text string here explaining why this is a valid thing to look out for' , ] , timeline_id : 'timeline_id' , timeline_title : 'timeline_title' , note : '# some investigation documentation' , version : 1 , query : 'user.name: root or user.name: admin' , } ) ; param ruleId export const getComplexRuleOutput = ( ruleId = 'rule-1' ) : Partial < RulesSchema > => ( { actions : [ ] , author : [ ] , created_by : 'elastic' , name : 'Complex Rule Query' , description : 'Complex Rule Query' , false_positives : [ 'https://www.example.com/some-article-about-a-false-positive' , 'some text string about why another condition could be a false positive' , ] , risk_score : 1 , risk_score_mapping : [ ] , rule_id : ruleId , filters : [ { query : { match_phrase : { 'host.name' : 'siem-windows' , } , } , } , ] , enabled : false , index : [ 'auditbeat-*' , 'filebeat-*' ] , immutable : false , interval : '5m' , output_index : '.siem-signals-default' , meta : { anything_you_want_ui_related_or_otherwise : { as_deep_structured_as_you_need : { any_data_type : { } , } , } , } , max_signals : 10 , tags : [ 'tag 1' , 'tag 2' , 'any tag you want' ] , to : 'now' , from : 'now-6m' , severity : 'high' , severity_mapping : [ ] , language : 'kuery' , type : 'query' , threat : [ { framework : 'MITRE ATT&CK' , tactic : { id : 'TA0040' , name : 'impact' , reference : 'https://attack.mitre.org/tactics/TA0040/' , } , technique : [ { id : 'T1499' , name : 'endpoint denial of service' , reference : 'https://attack.mitre.org/techniques/T1499/' , } , ] , } , { framework : 'Some other Framework you want' , tactic : { id : 'some-other-id' , name : 'Some other name' , reference : 'https://example.com' , } , technique : [ { id : 'some-other-id' , name : 'some other technique name' , reference : 'https://example.com' , } , ] , } , ] , references : [ 'http://www.example.com/some-article-about-attack' , 'Some plain text string here explaining why this is a valid thing to look out for' , ] , throttle : 'no_actions' , timeline_id : 'timeline_id' , timeline_title : 'timeline_title' , updated_by : 'elastic' , note : '# some investigation documentation' , version : 1 , query : 'user.name: root or user.name: admin' , exceptions_list : [ ] , } ) ; export const getWebHookAction = ( ) => ( { actionTypeId : '.webhook' , config : { method : 'post' , url : 'http://localhost' , } , secrets : { user : 'example' , password : 'example' , } , name : 'Some connector' , } ) ; export const getRuleWithWebHookAction = ( id , enabled = false , rule ? ) : CreateRulesSchema | UpdateRulesSchema => { const finalRule = rule != null ? { ... rule , enabled } : getSimpleRule ( 'rule-1' , enabled ) ; return { ... finalRule , throttle : 'rule' , actions : [ { group : 'default' , id , params : { body : '{}' , } , action_type_id : '.webhook' , } , ] , } ; } ; export const getSimpleRuleOutputWithWebHookAction = ( actionId ) : Partial < RulesSchema > => ( { ... getSimpleRuleOutput ( ) , throttle : 'rule' , actions : [ { action_type_id : '.webhook' , group : 'default' , id : actionId , params : { body : '{}' , } , } , ] , } ) ; export const waitFor = async ( functionToTest , functionName , log , maxTimeout = 800000 , timeoutWait = 250 ) : Promise < void > => { let found = false ; let numberOfTries = 0 ; const maxTries = Math . floor ( maxTimeout / timeoutWait ) ; while ( ! found && numberOfTries < maxTries ) { if ( await functionToTest ( ) ) { found = true ; } else { log . debug ( `Try number ${ numberOfTries } out of ${ maxTries } for function ${ functionName }` ) ; numberOfTries ++ ; } await new Promise ( ( resolveTimeout ) => setTimeout ( resolveTimeout , timeoutWait ) ) ; } if ( ! found ) { throw new Error ( `timed out waiting for function condition to be true within ${ functionName }` ) ; } } ; param esFunction param esFunctionName param log param retryCount param timeoutWait export const countDownES = async ( esFunction , esFunctionName , log , retryCount = 20 , timeoutWait = 250 ) : Promise < void > => { await countDownTest ( async ( ) => { const result = await esFunction ( ) ; if ( result . body . version_conflicts !== 0 ) { log . error ( `Version conflicts for ${ result . body . version_conflicts }` ) ; return false ; } else { return true ; } } , esFunctionName , log , retryCount , timeoutWait ) ; } ; param es export const refreshIndex = async ( es , index ? ) => { await es . indices . refresh ( { index , } ) ; } ; param functionToTest param name param log param retryCount param timeoutWait export const countDownTest = async ( functionToTest , name , log , retryCount = 20 , timeoutWait = 250 , ignoreThrow = false ) => { if ( retryCount > 0 ) { try { const passed = await functionToTest ( ) ; if ( ! passed ) { log . error ( `Failure trying to ${ name }, retries left are: ${ retryCount - 1 }` ) ; await new Promise ( ( resolve ) => setTimeout ( resolve , timeoutWait ) ) ; await countDownTest ( functionToTest , name , log , retryCount - 1 , timeoutWait , ignoreThrow ) ; } } catch ( err ) { if ( ignoreThrow ) { throw err ; } else { log . error ( `Failure trying to ${ name }, with exception message of: ${ err . message }, retries left are: ${ retryCount - 1 }` ) ; await new Promise ( ( resolve ) => setTimeout ( resolve , timeoutWait ) ) ; await countDownTest ( functionToTest , name , log , retryCount - 1 , timeoutWait , ignoreThrow ) ; } } } else { log . error ( `Could not ${ name }, no retries are left` ) ; } } ; param supertest param rule param log export const createRule = async ( supertest , log , rule ) : Promise < FullResponseSchema > => { const response = await supertest . post ( DETECTION_ENGINE_RULES_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( rule ) ; if ( response . status === 409 ) { if ( rule . rule_id != null ) { log . debug ( `Did not get an expected 200 "ok" when creating a rule (createRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; await deleteRule ( supertest , log , rule . rule_id ) ; const secondResponseTry = await supertest . post ( DETECTION_ENGINE_RULES_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( rule ) ; if ( secondResponseTry . status !== 200 ) { throw new Error ( `Unexpected non 200 ok when attempting to create a rule (second try): ${ JSON . stringify ( response . body ) }` ) ; } else { return secondResponseTry . body ; } } else { throw new Error ( 'When creating a rule found an unexpected conflict (404)' ) ; } } else if ( response . status !== 200 ) { throw new Error ( `Unexpected non 200 ok when attempting to create a rule: ${ JSON . stringify ( response . status ) }` ) ; } else { return response . body ; } } ; param supertest param ruleId param log export const deleteRule = async ( supertest , log , ruleId ) : Promise < FullResponseSchema > => { const response = await supertest . delete ( `${ DETECTION_ENGINE_RULES_URL }?rule_id=${ ruleId }` ) . set ( 'kbn-xsrf' , 'true' ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when deleting the rule (deleteRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest param rule export const createRuleWithAuth = async ( supertest , rule , auth ) : Promise < FullResponseSchema > => { const { body } = await supertest . post ( DETECTION_ENGINE_RULES_URL ) . set ( 'kbn-xsrf' , 'true' ) . auth ( auth . user , auth . pass ) . send ( rule ) ; return body ; } ; param supertest param rule export const updateRule = async ( supertest , log , updatedRule ) : Promise < FullResponseSchema > => { const response = await supertest . put ( DETECTION_ENGINE_RULES_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( updatedRule ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when updating a rule (updateRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest export const createNewAction = async ( supertest , log ) => { const response = await supertest . post ( '/api/actions/action' ) . set ( 'kbn-xsrf' , 'true' ) . send ( getWebHookAction ( ) ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when creating a new action. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest export const findImmutableRuleById = async ( supertest , log , ruleId ) : Promise < { page : number ; perPage : number ; total : number ; data : FullResponseSchema [ ] ; } > => { const response = await supertest . get ( `${ DETECTION_ENGINE_RULES_URL }/_find?filter=alert.attributes.tags: "${ INTERNAL_IMMUTABLE_KEY }:true" AND alert.attributes.tags: "${ INTERNAL_RULE_ID_KEY }:${ ruleId }"` ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when finding an immutable rule by id (findImmutableRuleById). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest export const getPrePackagedRulesStatus = async ( supertest , log ) : Promise < PrePackagedRulesAndTimelinesStatusSchema > => { const response = await supertest . get ( `${ DETECTION_ENGINE_PREPACKAGED_URL }/_status` ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when getting a pre-packaged rule status. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest param exceptionList param log export const createExceptionList = async ( supertest , log , exceptionList ) : Promise < ExceptionListSchema > => { const response = await supertest . post ( EXCEPTION_LIST_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( exceptionList ) ; if ( response . status === 409 ) { if ( exceptionList . list_id != null ) { log . error ( `When creating an exception list found an unexpected conflict (409) creating an exception list (createExceptionList), will attempt a cleanup and one time re-try. This usually indicates a bad cleanup or race condition within the tests: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; await deleteExceptionList ( supertest , log , exceptionList . list_id ) ; const secondResponseTry = await supertest . post ( EXCEPTION_LIST_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( exceptionList ) ; if ( secondResponseTry . status !== 200 ) { throw new Error ( `Unexpected non 200 ok when attempting to create an exception list (second try): ${ JSON . stringify ( response . body ) }` ) ; } else { return secondResponseTry . body ; } } else { throw new Error ( 'When creating an exception list found an unexpected conflict (404)' ) ; } } else if ( response . status !== 200 ) { throw new Error ( `Unexpected non 200 ok when attempting to create an exception list: ${ JSON . stringify ( response . status ) }` ) ; } else { return response . body ; } } ; param supertest param listId param log export const deleteExceptionList = async ( supertest , log , listId ) : Promise < FullResponseSchema > => { const response = await supertest . delete ( `${ EXCEPTION_LIST_URL }?list_id=${ listId }` ) . set ( 'kbn-xsrf' , 'true' ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when deleting an exception list (deleteExceptionList). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest param exceptionListItem param log export const createExceptionListItem = async ( supertest , log , exceptionListItem ) : Promise < ExceptionListItemSchema > => { const response = await supertest . post ( EXCEPTION_LIST_ITEM_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( exceptionListItem ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when creating an exception list item (createExceptionListItem). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; param supertest param rule export const getRule = async ( supertest , log , ruleId ) : Promise < RulesSchema > => { const response = await supertest . get ( `${ DETECTION_ENGINE_RULES_URL }?rule_id=${ ruleId }` ) . set ( 'kbn-xsrf' , 'true' ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when getting a rule (getRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body ; } ; export const waitForAlertToComplete = async ( supertest , log , id ) : Promise < void > => { await waitFor ( async ( ) => { const response = await supertest . get ( `/api/alerts/alert/${ id }/state` ) . set ( 'kbn-xsrf' , 'true' ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when waiting for an alert to complete (waitForAlertToComplete). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return response . body . previousStartedAt != null ; } , 'waitForAlertToComplete' , log ) ; } ; param supertest export const waitForRuleSuccessOrStatus = async ( supertest , log , id , status = 'succeeded' , afterDate ? ) : Promise < void > => { await waitFor ( async ( ) => { try { const response = await supertest . get ( DETECTION_ENGINE_RULES_URL ) . set ( 'kbn-xsrf' , 'true' ) . query ( { id } ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when waiting for a rule success or status (waitForRuleSuccessOrStatus). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } const rule = response . body ; if ( rule ?. status !== status ) { log . debug ( `Did not get an expected status of ${ status } while waiting for a rule success or status for rule id ${ id } (waitForRuleSuccessOrStatus). Will continue retrying until status is found. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return ( rule != null && rule . status === status && ( afterDate ? new Date ( rule . status_date ) > afterDate : true ) ) ; } catch ( e ) { if ( ( e as Error ) . message . includes ( 'got 503 "Service Unavailable"' ) ) { return false ; } throw e ; } } , 'waitForRuleSuccessOrStatus' , log ) ; } ; param supertest param numberOfSignals export const waitForSignalsToBePresent = async ( supertest , log , numberOfSignals = 1 , signalIds ) : Promise < void > => { await waitFor ( async ( ) => { const signalsOpen = await getSignalsByIds ( supertest , log , signalIds , numberOfSignals ) ; return signalsOpen . hits . hits . length >= numberOfSignals ; } , 'waitForSignalsToBePresent' , log , 20000 , 250 ) ; } ; param supertest export const getSignalsByRuleIds = async ( supertest , log , ruleIds ) : Promise < estypes . SearchResponse < RACAlert > > => { const response = await supertest . post ( DETECTION_ENGINE_QUERY_SIGNALS_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( getQuerySignalsRuleId ( ruleIds ) ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when getting a signal by rule_id (getSignalsByRuleIds). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } const { body : signalsOpen } = response ; return signalsOpen ; } ; param supertest param ids export const getSignalsByIds = async ( supertest , log , ids , size ? ) : Promise < estypes . SearchResponse < RACAlert > > => { const response = await supertest . post ( DETECTION_ENGINE_QUERY_SIGNALS_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( getQuerySignalsId ( ids , size ) ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when getting a signal by id. CI issues could happen (getSignalsByIds). Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } const { body : signalsOpen } = response ; return signalsOpen ; } ; param supertest param ids export const getSignalsById = async ( supertest , log , id ) : Promise < estypes . SearchResponse < RACAlert > > => { const response = await supertest . post ( DETECTION_ENGINE_QUERY_SIGNALS_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( getQuerySignalsId ( [ id ] ) ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when getting signals by id (getSignalsById). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } const { body : signalsOpen } = response ; return signalsOpen ; } ; export const installPrePackagedRules = async ( supertest , log ) : Promise < void > => { await countDownTest ( async ( ) => { const { status , body } = await supertest . put ( DETECTION_ENGINE_PREPACKAGED_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( ) ; if ( status !== 200 ) { log . debug ( `Did not get an expected 200 "ok" when installing pre-packaged rules (installPrePackagedRules) yet. Retrying until we get a 200 "ok". body: ${ JSON . stringify ( body ) }, status: ${ JSON . stringify ( status ) }` ) ; } return status === 200 ; } , 'installPrePackagedRules' , log ) ; } ; param supertest param endpointEntries param osTypes export const createContainerWithEndpointEntries = async ( supertest , log , endpointEntries ) : Promise < ListArray > => { if ( endpointEntries . length === 0 ) { return [ ] ; } const { id , list_id , namespace_type , type } = await createExceptionList ( supertest , log , { description : 'endpoint description' , list_id : 'endpoint_list' , name : 'endpoint_list' , type : 'endpoint' , } ) ; await Promise . all ( endpointEntries . map ( ( endpointEntry ) => { const exceptionListItem = { description : 'endpoint description' , entries : endpointEntry . entries , list_id : 'endpoint_list' , name : 'endpoint_list' , os_types : endpointEntry . osTypes , type : 'simple' , } ; return createExceptionListItem ( supertest , log , exceptionListItem ) ; } ) ) ; await waitFor ( async ( ) => { const { body } = await supertest . get ( `${ EXCEPTION_LIST_ITEM_URL }/_find?list_id=${ list_id }` ) ; return body . data . length === endpointEntries . length ; } , `within createContainerWithEndpointEntries ${ EXCEPTION_LIST_ITEM_URL }/_find?list_id=${ list_id }` , log ) ; return [ { id , list_id , namespace_type , type , } , ] ; } ; param supertest param entries param osTypes export const createContainerWithEntries = async ( supertest , log , entries ) : Promise < ListArray > => { if ( entries . length === 0 ) { return [ ] ; } const { id , list_id , namespace_type , type } = await createExceptionList ( supertest , log , { description : 'some description' , list_id : 'some-list-id' , name : 'some name' , type : 'detection' , } ) ; await Promise . all ( entries . map ( ( entry ) => { const exceptionListItem = { description : 'some description' , list_id : 'some-list-id' , name : 'some name' , type : 'simple' , entries : entry , } ; return createExceptionListItem ( supertest , log , exceptionListItem ) ; } ) ) ; await waitFor ( async ( ) => { const { body } = await supertest . get ( `${ EXCEPTION_LIST_ITEM_URL }/_find?list_id=${ list_id }` ) ; return body . data . length === entries . length ; } , `within createContainerWithEntries ${ EXCEPTION_LIST_ITEM_URL }/_find?list_id=${ list_id }` , log ) ; return [ { id , list_id , namespace_type , type , } , ] ; } ; param supertest param rule param entries param endpointEntries param osTypes export const createRuleWithExceptionEntries = async ( supertest , log , rule , entries , endpointEntries ? ) : Promise < FullResponseSchema > => { const maybeExceptionList = await createContainerWithEntries ( supertest , log , entries ) ; const maybeEndpointList = await createContainerWithEndpointEntries ( supertest , log , endpointEntries ?? [ ] ) ; const ruleWithException = { ... rule , enabled : false , exceptions_list : [ ... maybeExceptionList , ... maybeEndpointList ] , } ; const ruleResponse = await createRule ( supertest , log , ruleWithException ) ; const response = await supertest . patch ( DETECTION_ENGINE_RULES_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( { rule_id : ruleResponse . rule_id , enabled : true } ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when patching a rule with exception entries (createRuleWithExceptionEntries). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return ruleResponse ; } ; export const getIndexNameFromLoad = ( loadResponse ) : string => { const indexNames = Object . keys ( loadResponse ) ; if ( indexNames . length > 1 ) { throw new Error ( `expected load response to contain one index, but contained multiple: [${ indexNames }]` ) ; } return indexNames [ 0 ] ; } ; param esClient elasticsearch  Client param index export const waitForIndexToPopulate = async ( es , log , index ) : Promise < void > => { await waitFor ( async ( ) => { const response = await es . count ( { index } ) ; return response . count > 0 ; } , `waitForIndexToPopulate: ${ index }` , log ) ; } ; export const deleteMigrations = async ( { ids , kbnClient , } ) : Promise < void > => { await Promise . all ( ids . map ( ( id ) => kbnClient . savedObjects . delete ( { id , type : signalsMigrationType , } ) ) ) ; } ; interface CreateMigrationResponse { index : string ; migration_index : string ; migration_id : string ; } export const startSignalsMigration = async ( { indices , supertest , log , } ) : Promise < CreateMigrationResponse [ ] > => { const response = await supertest . post ( DETECTION_ENGINE_SIGNALS_MIGRATION_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( { index : indices } ) ; const { body : { indices : created } , } = response ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when starting a signals migration (startSignalsMigration). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return created ; } ; interface FinalizeMigrationResponse { id : string ; completed ? : boolean ; error ? : unknown ; } export const finalizeSignalsMigration = async ( { migrationIds , supertest , log , } ) : Promise < FinalizeMigrationResponse [ ] > => { const response = await supertest . post ( DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL ) . set ( 'kbn-xsrf' , 'true' ) . send ( { migration_ids : migrationIds } ) ; const { body : { migrations } , } = response ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when finalizing signals migration (finalizeSignalsMigration). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return migrations ; } ; export const getOpenSignals = async ( supertest , log , es , rule ) => { await waitForRuleSuccessOrStatus ( supertest , log , rule . id ) ; await refreshIndex ( es , '.alerts-security.alerts-default*' ) ; return getSignalsByIds ( supertest , log , [ rule . id ] ) ; } ; /**
 * Cluster stats URL. Replace this with any from kibana core if there is ever a constant there for this.
 */ export const getStatsUrl = ( ) : string => '/api/telemetry/v2/clusters/_stats' ; param body returns export const getDetectionMetricsFromBody = ( body ) : DetectionMetrics => { return body [ 0 ] . stats . stack_stats . kibana . plugins . security_solution . detectionMetrics ; } ; param supertest returns export const getStats = async ( supertest , log ) : Promise < DetectionMetrics > => { const response = await supertest . post ( getStatsUrl ( ) ) . set ( 'kbn-xsrf' , 'true' ) . send ( { unencrypted : true , refreshCache : true } ) ; if ( response . status !== 200 ) { log . error ( `Did not get an expected 200 "ok" when getting the stats for detections. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${ JSON . stringify ( response . body ) }, status: ${ JSON . stringify ( response . status ) }` ) ; } return getDetectionMetricsFromBody ( response . body ) ; } ; param ruleId param enabled export const getSimpleThreatMatch = ( ruleId = 'rule-1' , enabled = false ) : ThreatMatchCreateSchema => ( { description : 'Detecting root and admin users' , name : 'Query with a rule id' , severity : 'high' , enabled , index : [ 'auditbeat-*' ] , type : 'threat_match' , risk_score : 55 , language : 'kuery' , rule_id : ruleId , from : '1900-01-01T00:00:00.000Z' , query : '*:*' , threat_query : '*:*' , threat_index : [ 'auditbeat-*' ] , threat_mapping : [ { entries : [ { field : 'host.name' , value : 'host.name' , type : 'mapping' , } , ] , } , ] , threat_filters : [ ] , } ) ;