[Security Solution][API testing] Move and restructures Rule execution logic (#170765)

## Summary

Following the initial work in this
https://github.com/elastic/kibana/pull/166755
 
- Addresses part of https://github.com/elastic/kibana/issues/151902 for
rule execution logic

- Moved the utility files associated with rule execution logic to the
new directory `security_solution_api_integration`. Files not actively
used in the previous folder were moved, while duplicate files remained
in their original positions.
- Updated the CodeOwner file for the newly moved tests

- Old/new group details, decisions, and execution time are mentioned in
this[document](https://docs.google.com/document/d/1CRFfDWMzw3ob03euWIvT4-IoiLXjoiPWI8mTBqP4Zks/edit)
- Added new `Alert` archive for version `8.8.0`

- Resolved the issue with the `query.ts` test where the execution logic
is executed last, encompassing the "query" test because it was unloading
the alerts document and led to failures in subsequent tests.

- For `Alert As Data` in **Serverless** the alert ancestor will be a
data-stream however in **ESS** will be
`.internal.alerts-security.alerts-default-000001'`


| Action | File | New Path if moved |
|--------|------|----------|
| Delete| security_and_spaces/rule_execution_logic| - |
| Delete|security_and_spaces/group5  | - |
|
Move|detection_engine_api_integration/security_and_spaces/group5|detections_response/default_license/rule_execution_logic/keyword_family|
|
Move|detection_engine_api_integration/security_and_spaces/rule_execution_logic|
detections_response/default_license/rule_execution_logic/execution_logic
|
| Move
|detection_engine_api_integration/security_and_spaces/group1/ignore_fields|
detections_response/default_license/rule_execution_logic/ignore_fields.ts|
|
Move|detection_engine_api_integration/security_and_spaces/group1/runtime|
detections_response/default_license/rule_execution_logic/runtime.ts |
|
Move|detection_engine_api_integration/security_and_spaces/group1/timestamps|
detections_response/default_license/rule_execution_logic/timestamps.ts|

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Wafaa Nasr 2023-11-16 16:14:02 +01:00 committed by GitHub
parent c713b91e66
commit 2b136a2d77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 10019 additions and 2186 deletions

View file

@ -225,9 +225,7 @@ enabled:
- x-pack/test/detection_engine_api_integration/basic/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/group4/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/group5/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts
- x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts
- x-pack/test/disable_ems/config.ts
- x-pack/test/encrypted_saved_objects_api_integration/config.ts
- x-pack/test/examples/config.ts
@ -474,3 +472,5 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/configs/ess.config.ts

View file

@ -26,9 +26,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
loadTestFile(require.resolve('./perform_bulk_action_dry_run'));
loadTestFile(require.resolve('./patch_rules'));
loadTestFile(require.resolve('./read_privileges'));
loadTestFile(require.resolve('./timestamps'));
loadTestFile(require.resolve('./runtime'));
loadTestFile(require.resolve('./throttle'));
loadTestFile(require.resolve('./ignore_fields'));
});
};

View file

@ -1,18 +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 { FtrConfigProviderContext } from '@kbn/test';
// eslint-disable-next-line import/no-default-export
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../config.base.ts'));
return {
...functionalConfig.getAll(),
testFiles: [require.resolve('.')],
};
}

View file

@ -1,15 +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 { FtrProviderContext } from '../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile }: FtrProviderContext): void => {
describe('detection engine api security and spaces enabled - Group 5', function () {
loadTestFile(require.resolve('./keyword_family'));
});
};

View file

@ -1,28 +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 { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { ALERT_LAST_DETECTED, ALERT_START } from '@kbn/rule-data-utils';
export const removeRandomValuedProperties = (alert: DetectionAlert | undefined) => {
if (!alert) {
return undefined;
}
const {
'kibana.version': version,
'kibana.alert.rule.execution.uuid': execUuid,
'kibana.alert.rule.uuid': uuid,
'@timestamp': timestamp,
'kibana.alert.rule.created_at': createdAt,
'kibana.alert.rule.updated_at': updatedAt,
'kibana.alert.uuid': alertUuid,
[ALERT_START]: alertStart,
[ALERT_LAST_DETECTED]: lastDetected,
...restOfAlert
} = alert;
return restOfAlert;
};

View file

@ -39,7 +39,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -25,7 +25,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -0,0 +1,422 @@
{
"type": "doc",
"value": {
"id": "eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1",
"index": ".alerts-security.alerts-default",
"source": {
"@timestamp": "2023-04-27T11:03:57.906Z",
"Endpoint": {
"capabilities": [
"isolation",
"kill_process",
"suspend_process",
"running_processes",
"get_file",
"execute"
],
"configuration": {
"isolation": true
},
"policy": {
"applied": {
"endpoint_policy_version": 3,
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A",
"name": "With Eventing",
"status": "success",
"version": 5
}
},
"state": {
"isolation": true
},
"status": "enrolled"
},
"agent": {
"id": "b563ce99-e373-4a1f-a5fe-97e956140aeb",
"type": "endpoint",
"version": "8.8.0"
},
"data_stream": {
"dataset": "endpoint.alerts",
"namespace": "default",
"type": "logs"
},
"dll": [
{
"Ext": {
"compile_time": 1534424710,
"malware_classification": {
"identifier": "Whitelisted",
"score": 0,
"threshold": 0,
"version": "3.0.0"
},
"mapped_address": 5362483200,
"mapped_size": 0
},
"code_signature": {
"subject_name": "Cybereason Inc",
"trusted": true
},
"hash": {
"md5": "1f2d082566b0fc5f2c238a5180db7451",
"sha1": "ca85243c0af6a6471bdaa560685c51eefd6dbc0d",
"sha256": "8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2"
},
"path": "C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe",
"pe": {
"architecture": "x64"
}
}
],
"ecs": {
"version": "1.4.0"
},
"elastic": {
"agent": {
"id": "b563ce99-e373-4a1f-a5fe-97e956140aeb"
}
},
"event.action": "creation",
"event.agent_id_status": "auth_metadata_missing",
"event.category": "malware",
"event.code": "malicious_file",
"event.dataset": "endpoint",
"event.id": "b28993d4-8b8a-4f0f-9f54-84a89bad66ae",
"event.ingested": "2023-04-27T10:58:03Z",
"event.kind": "signal",
"event.module": "endpoint",
"event.sequence": 5826,
"event.type": "creation",
"file": {
"Ext": {
"code_signature": [
{
"subject_name": "bad signer",
"trusted": false
}
],
"malware_classification": {
"identifier": "endpointpe",
"score": 1,
"threshold": 0.66,
"version": "3.0.33"
},
"quarantine_message": "fake quarantine message",
"quarantine_result": true,
"temp_file_path": "C:/temp/fake_malware.exe"
},
"accessed": 1682752652103,
"created": 1682752652103,
"hash": {
"md5": "fake file md5",
"sha1": "fake file sha1",
"sha256": "fake file sha256"
},
"mtime": 1682752652103,
"name": "fake_malware.exe",
"owner": "SYSTEM",
"path": "C:/fake_malware.exe",
"size": 3456
},
"user": {
"name": "user1"
},
"host": {
"architecture": "wtnozeqvub",
"hostname": "Host-fwarau82er",
"id": "4260adf9-5e63-445d-92c6-e03359bcd342",
"ip": [
"10.249.37.72",
"10.150.39.243",
"10.186.17.170"
],
"mac": [
"f5-f-97-dc-20-67",
"b5-56-ca-98-81-ca",
"22-86-39-4c-87-33"
],
"name": "Host-fwarau82er",
"os": {
"Ext": {
"variant": "Darwin"
},
"family": "Darwin",
"full": "macOS Monterey",
"name": "macOS",
"platform": "macOS",
"version": "12.6.1"
}
},
"kibana.alert.ancestors": [
{
"depth": 0,
"id": "vT9cwocBh3b8EMpD8lsi",
"index": ".ds-logs-endpoint.alerts-default-2023.04.27-000001",
"type": "event"
}
],
"kibana.alert.depth": 1,
"kibana.alert.last_detected": "2023-04-27T11:03:57.993Z",
"kibana.alert.original_event.action": "creation",
"kibana.alert.original_event.agent_id_status": "auth_metadata_missing",
"kibana.alert.original_event.category": "malware",
"kibana.alert.original_event.code": "malicious_file",
"kibana.alert.original_event.dataset": "endpoint",
"kibana.alert.original_event.id": "b28993d4-8b8a-4f0f-9f54-84a89bad66ae",
"kibana.alert.original_event.ingested": "2023-04-27T10:58:03Z",
"kibana.alert.original_event.kind": "alert",
"kibana.alert.original_event.module": "endpoint",
"kibana.alert.original_event.sequence": 5826,
"kibana.alert.original_event.type": "creation",
"kibana.alert.original_time": "2023-04-29T07:17:32.103Z",
"kibana.alert.reason": "malware event with process malware writer, file fake_malware.exe, on Host-fwarau82er created medium alert Endpoint Security.",
"kibana.alert.risk_score": 47,
"kibana.alert.rule.actions": [
],
"kibana.alert.rule.author": [
"Elastic"
],
"kibana.alert.rule.category": "Custom Query Rule",
"kibana.alert.rule.consumer": "siem",
"kibana.alert.rule.created_at": "2023-04-27T10:58:27.546Z",
"kibana.alert.rule.created_by": "elastic",
"kibana.alert.rule.description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.",
"kibana.alert.rule.enabled": true,
"kibana.alert.rule.exceptions_list": [
{
"id": "endpoint_list",
"list_id": "endpoint_list",
"namespace_type": "agnostic",
"type": "endpoint"
}
],
"kibana.alert.rule.execution.uuid": "ebf843ff-e0e1-47f8-9ed2-cc8066afbcef",
"kibana.alert.rule.false_positives": [
],
"kibana.alert.rule.from": "now-10m",
"kibana.alert.rule.immutable": true,
"kibana.alert.rule.indices": [
"logs-endpoint.alerts-*"
],
"kibana.alert.rule.interval": "5m",
"kibana.alert.rule.license": "Elastic License v2",
"kibana.alert.rule.max_signals": 10000,
"kibana.alert.rule.name": "Endpoint Security",
"kibana.alert.rule.parameters": {
"author": [
"Elastic"
],
"description": "Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.",
"exceptions_list": [
{
"id": "endpoint_list",
"list_id": "endpoint_list",
"namespace_type": "agnostic",
"type": "endpoint"
}
],
"false_positives": [
],
"from": "now-10m",
"immutable": true,
"index": [
"logs-endpoint.alerts-*"
],
"language": "kuery",
"license": "Elastic License v2",
"max_signals": 10000,
"query": "event.kind:alert and event.module:(endpoint and not endgame)\n",
"references": [
],
"related_integrations": [
{
"package": "endpoint",
"version": "^8.2.0"
}
],
"required_fields": [
{
"ecs": true,
"name": "event.kind",
"type": "keyword"
},
{
"ecs": true,
"name": "event.module",
"type": "keyword"
}
],
"risk_score": 47,
"risk_score_mapping": [
{
"field": "event.risk_score",
"operator": "equals",
"value": ""
}
],
"rule_id": "9a1a2dae-0b5f-4c3d-8305-a268d404c306",
"rule_name_override": "message",
"setup": "",
"severity": "medium",
"severity_mapping": [
{
"field": "event.severity",
"operator": "equals",
"severity": "low",
"value": "21"
},
{
"field": "event.severity",
"operator": "equals",
"severity": "medium",
"value": "47"
},
{
"field": "event.severity",
"operator": "equals",
"severity": "high",
"value": "73"
},
{
"field": "event.severity",
"operator": "equals",
"severity": "critical",
"value": "99"
}
],
"threat": [
],
"timestamp_override": "event.ingested",
"to": "now",
"type": "query",
"version": 101
},
"kibana.alert.rule.producer": "siem",
"kibana.alert.rule.references": [
],
"kibana.alert.rule.revision": 0,
"kibana.alert.rule.risk_score": 47,
"kibana.alert.rule.risk_score_mapping": [
{
"field": "event.risk_score",
"operator": "equals",
"value": ""
}
],
"kibana.alert.rule.rule_id": "9a1a2dae-0b5f-4c3d-8305-a268d404c306",
"kibana.alert.rule.rule_name_override": "message",
"kibana.alert.rule.rule_type_id": "siem.queryRule",
"kibana.alert.rule.severity": "medium",
"kibana.alert.rule.severity_mapping": [
{
"field": "event.severity",
"operator": "equals",
"severity": "low",
"value": "21"
},
{
"field": "event.severity",
"operator": "equals",
"severity": "medium",
"value": "47"
},
{
"field": "event.severity",
"operator": "equals",
"severity": "high",
"value": "73"
},
{
"field": "event.severity",
"operator": "equals",
"severity": "critical",
"value": "99"
}
],
"kibana.alert.rule.tags": [
"Elastic",
"Endpoint Security"
],
"kibana.alert.rule.threat": [
],
"kibana.alert.rule.timestamp_override": "event.ingested",
"kibana.alert.rule.to": "now",
"kibana.alert.rule.type": "query",
"kibana.alert.rule.updated_at": "2023-04-27T10:58:27.546Z",
"kibana.alert.rule.updated_by": "elastic",
"kibana.alert.rule.uuid": "7015a3e2-e4ea-11ed-8c11-49608884878f",
"kibana.alert.rule.version": 101,
"kibana.alert.severity": "medium",
"kibana.alert.start": "2023-04-27T11:03:57.993Z",
"kibana.alert.status": "active",
"kibana.alert.url": "http://localhost:5601/app/security/alerts/redirect/eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1?index=.alerts-security.alerts-default&timestamp=2023-04-27T11:03:57.906Z",
"kibana.alert.uuid": "eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1",
"kibana.alert.workflow_status": "open",
"kibana.space_ids": [
"default"
],
"kibana.version": "8.8.0",
"process": {
"Ext": {
"ancestry": [
"qa5jgw1wr7",
"5k1hclygc6"
],
"code_signature": [
{
"subject_name": "bad signer",
"trusted": false
}
],
"token": {
"domain": "NT AUTHORITY",
"integrity_level": 16384,
"integrity_level_name": "system",
"privileges": [
{
"description": "Replace a process level token",
"enabled": false,
"name": "SeAssignPrimaryTokenPrivilege"
}
],
"sid": "S-1-5-18",
"type": "tokenPrimary",
"user": "SYSTEM"
},
"user": "SYSTEM"
},
"entity_id": "nqh8ts6ves",
"entry_leader": {
"entity_id": "jnm38bel0w",
"name": "fake entry",
"pid": 791
},
"executable": "C:/malware.exe",
"group_leader": {
"entity_id": "jnm38bel0w",
"name": "fake leader",
"pid": 848
},
"hash": {
"md5": "fake md5",
"sha1": "fake sha1",
"sha256": "fake sha256"
},
"name": "malware writer",
"parent": {
"entity_id": "qa5jgw1wr7",
"pid": 1
},
"pid": 2,
"session_leader": {
"entity_id": "jnm38bel0w",
"name": "fake session",
"pid": 909
},
"start": 1682752652103,
"uptime": 0
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -7027,7 +7027,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1",
"mapping": {

View file

@ -108,7 +108,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -13,7 +13,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -30,7 +30,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -20,7 +20,6 @@
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_replicas": "1",
"number_of_shards": "1"
}

View file

@ -11,15 +11,18 @@ export interface CreateTestConfigOptions {
kbnTestServerArgs?: string[];
kbnTestServerEnv?: Record<string, string>;
}
import { services } from '../../../../test_serverless/api_integration/services';
export function createTestConfig(options: CreateTestConfigOptions) {
return async ({ readConfigFile }: FtrConfigProviderContext) => {
const svlSharedConfig = await readConfigFile(
require.resolve('../../../../test_serverless/shared/config.base.ts')
);
return {
...svlSharedConfig.getAll(),
services: {
...services,
},
kbnTestServer: {
...svlSharedConfig.get('kbnTestServer'),
serverArgs: [

View file

@ -0,0 +1,418 @@
{
"type": "doc",
"value": {
"id": "978783",
"index": "filebeat-8.0.0-2021.01.26-000001",
"source": {
"@timestamp": "2021-01-26T11:09:05.529Z",
"agent": {
"ephemeral_id": "b7b56c3e-1f27-4c69-96f4-aa9ca47888d0",
"id": "69acb5f0-1e79-4cfe-a4dc-e0dbf229ff51",
"name": "MacBook-Pro-de-Gloria.local",
"type": "filebeat",
"version": "8.0.0"
},
"ecs": {
"version": "1.6.0"
},
"event": {
"category": "threat",
"created": "2021-01-26T11:09:05.529Z",
"dataset": "ti_abusech.malware",
"ingested": "2021-01-26T11:09:06.595350Z",
"kind": "enrichment",
"module": "threatintel",
"reference": "https://urlhaus.abuse.ch/url/978783/",
"type": "indicator"
},
"fileset": {
"name": "abuseurl"
},
"input": {
"type": "httpjson"
},
"service": {
"type": "threatintel"
},
"tags": [
"threatintel-abuseurls",
"forwarded"
],
"threat": {
"indicator": {
"description": "domain should match the auditbeat hosts' data's source.ip",
"domain": "159.89.119.67",
"first_seen": "2021-01-26T11:09:04.000Z",
"provider": "geenensp",
"type": "url",
"url": {
"full": "http://159.89.119.67:59600/bin.sh",
"scheme": "http"
}
}
},
"threatintel": {
"abuseurl": {
"blacklists": {
"spamhaus_dbl": "not listed",
"surbl": "not listed"
},
"larted": false,
"tags": null,
"threat": "malware_download",
"url_status": "online"
}
}
}
}
}
{
"type": "doc",
"value": {
"id": "978784",
"index": "filebeat-8.0.0-2021.01.26-000001",
"source": {
"@timestamp": "2021-01-26T11:09:05.529Z",
"agent": {
"ephemeral_id": "b7b56c3e-1f27-4c69-96f4-aa9ca47888d0",
"id": "69acb5f0-1e79-4cfe-a4dc-e0dbf229ff51",
"name": "MacBook-Pro-de-Gloria.local",
"type": "filebeat",
"version": "8.0.0"
},
"ecs": {
"version": "1.6.0"
},
"event": {
"category": "threat",
"created": "2021-01-26T11:09:05.529Z",
"dataset": "ti_abusech.malware",
"ingested": "2021-01-26T11:09:06.616763Z",
"kind": "enrichment",
"module": "threatintel",
"reference": "https://urlhaus.abuse.ch/url/978782/",
"type": "indicator"
},
"fileset": {
"name": "abuseurl"
},
"input": {
"type": "httpjson"
},
"service": {
"type": "threatintel"
},
"tags": [
"threatintel-abuseurls",
"forwarded"
],
"threat": {
"indicator": {
"description": "this should not match the auditbeat hosts data",
"ip": "125.46.136.106",
"first_seen": "2021-01-26T11:06:03.000Z",
"provider": "geenensp",
"type": "ip"
}
},
"threatintel": {
"abuseurl": {
"blacklists": {
"spamhaus_dbl": "not listed",
"surbl": "not listed"
},
"larted": true,
"tags": [
"32-bit",
"elf",
"mips"
],
"threat": "malware_download",
"url_status": "online"
}
}
}
}
}
{
"type": "doc",
"value": {
"id": "978785",
"index": "filebeat-8.0.0-2021.01.26-000001",
"source": {
"@timestamp": "2021-01-26T11:09:05.529Z",
"agent": {
"ephemeral_id": "b7b56c3e-1f27-4c69-96f4-aa9ca47888d0",
"id": "69acb5f0-1e79-4cfe-a4dc-e0dbf229ff51",
"name": "MacBook-Pro-de-Gloria.local",
"type": "filebeat",
"version": "8.0.0"
},
"ecs": {
"version": "1.6.0"
},
"event": {
"category": "threat",
"created": "2021-01-26T11:09:05.529Z",
"dataset": "ti_abusech.malware",
"ingested": "2021-01-26T11:09:06.616763Z",
"kind": "enrichment",
"module": "threatintel",
"reference": "https://urlhaus.abuse.ch/url/978782/",
"type": "indicator"
},
"fileset": {
"name": "abuseurl"
},
"input": {
"type": "httpjson"
},
"service": {
"type": "threatintel"
},
"tags": [
"threatintel-abuseurls",
"forwarded"
],
"threat": {
"indicator": {
"description": "this should match auditbeat/hosts on both port and ip",
"ip": "45.115.45.3",
"port": 57324,
"first_seen": "2021-01-26T11:06:03.000Z",
"provider": "geenensp",
"type": "url"
}
},
"threatintel": {
"abuseurl": {
"blacklists": {
"spamhaus_dbl": "not listed",
"surbl": "not listed"
},
"larted": true,
"tags": [
"32-bit",
"elf",
"mips"
],
"threat": "malware_download",
"url_status": "online"
}
}
}
}
}
{
"type": "doc",
"value": {
"id": "978787",
"index": "filebeat-8.0.0-2021.01.26-000001",
"source": {
"@timestamp": "2021-01-26T11:09:05.529Z",
"agent": {
"ephemeral_id": "b7b56c3e-1f27-4c69-96f4-aa9ca47888d0",
"id": "69acb5f0-1e79-4cfe-a4dc-e0dbf229ff51",
"name": "MacBook-Pro-de-Gloria.local",
"type": "filebeat",
"version": "8.0.0"
},
"ecs": {
"version": "1.6.0"
},
"event": {
"category": "threat",
"created": "2021-01-26T11:09:05.529Z",
"dataset": "ti_abusech.malware",
"ingested": "2021-01-26T11:09:06.616763Z",
"kind": "enrichment",
"module": "threatintel",
"reference": "https://urlhaus.abuse.ch/url/978782/",
"type": "indicator"
},
"fileset": {
"name": "abuseurl"
},
"input": {
"type": "httpjson"
},
"service": {
"type": "threatintel"
},
"tags": [
"threatintel-abuseurls",
"forwarded"
],
"threat": {
"indicator": {
"description": "this should match auditbeat/hosts on ip",
"ip": "45.115.45.3",
"first_seen": "2021-01-26T11:06:03.000Z",
"provider": "other_provider",
"type": "ip"
}
},
"threatintel": {
"abuseurl": {
"blacklists": {
"spamhaus_dbl": "not listed",
"surbl": "not listed"
},
"larted": true,
"tags": [
"32-bit",
"elf",
"mips"
],
"threat": "malware_download",
"url_status": "online"
}
}
}
}
}
{
"type": "doc",
"value": {
"id": "978766",
"index": "filebeat-8.0.0-2021.01.26-000001",
"source": {
"@timestamp": "2021-01-26T11:09:05.529Z",
"agent": {
"ephemeral_id": "b7b56c3e-1f27-4c69-96f4-aa9ca47888d0",
"id": "69acb5f0-1e79-4cfe-a4dc-e0dbf229ff51",
"name": "MacBook-Pro-de-Gloria.local",
"type": "filebeat",
"version": "8.0.0"
},
"ecs": {
"version": "1.6.0"
},
"event": {
"category": "threat",
"created": "2021-01-26T11:09:05.529Z",
"dataset": "ti_abusech.malware",
"ingested": "2021-01-26T11:09:06.595350Z",
"kind": "enrichment",
"module": "threatintel",
"reference": "https://urlhaus.abuse.ch/url/978783/",
"type": "indicator"
},
"fileset": {
"name": "abuseurl"
},
"input": {
"type": "httpjson"
},
"service": {
"type": "threatintel"
},
"tags": [
"threatintel-abuseurls",
"forwarded"
],
"threat": {
"indicator": {
"description": "domain should match the auditbeat hosts' data's source.ip",
"domain": "172.16.0.0",
"ip": "8.8.8.8",
"port": 777,
"first_seen": "2021-01-26T11:09:04.000Z",
"provider": "geenensp",
"type": "url",
"url": {
"full": "http://159.89.119.67:59600/bin.sh",
"scheme": "http"
}
}
},
"threatintel": {
"abuseurl": {
"blacklists": {
"spamhaus_dbl": "not listed",
"surbl": "not listed"
},
"larted": false,
"tags": null,
"threat": "malware_download",
"url_status": "online"
}
}
}
}
}
{
"type": "doc",
"value": {
"id": "978767",
"index": "filebeat-8.0.0-2021.01.26-000001",
"source": {
"@timestamp": "2021-01-26T11:09:05.529Z",
"agent": {
"ephemeral_id": "b7b56c3e-1f27-4c69-96f4-aa9ca47888d0",
"id": "69acb5f0-1e79-4cfe-a4dc-e0dbf229ff51",
"name": "MacBook-Pro-de-Gloria.local",
"type": "filebeat",
"version": "8.0.0"
},
"ecs": {
"version": "1.6.0"
},
"event": {
"category": "threat",
"created": "2021-01-26T11:09:05.529Z",
"dataset": "ti_abusech.malware",
"ingested": "2021-01-26T11:09:06.595350Z",
"kind": "enrichment",
"module": "threatintel",
"reference": "https://urlhaus.abuse.ch/url/978783/",
"type": "indicator"
},
"fileset": {
"name": "abuseurl"
},
"input": {
"type": "httpjson"
},
"service": {
"type": "threatintel"
},
"tags": [
"threatintel-abuseurls",
"forwarded"
],
"threat": {
"indicator": {
"description": "domain should match the auditbeat hosts' data's source.ip",
"domain": "172.16.0.0",
"ip": "9.9.9.9",
"port": 123,
"first_seen": "2021-01-26T11:09:04.000Z",
"provider": "geenensp",
"type": "url",
"url": {
"full": "http://159.89.119.67:59600/bin.sh",
"scheme": "http"
}
}
},
"threatintel": {
"abuseurl": {
"blacklists": {
"spamhaus_dbl": "not listed",
"surbl": "not listed"
},
"larted": false,
"tags": null,
"threat": "malware_download",
"url_status": "online"
}
}
}
}
}

View file

@ -0,0 +1,240 @@
{
"type": "index",
"value": {
"aliases": {},
"index": "filebeat-8.0.0-2021.01.26-000001",
"mappings": {
"_meta": {
"beat": "filebeat",
"version": "7.0.0"
},
"properties": {
"@timestamp": {
"type": "date"
},
"@version": {
"ignore_above": 1024,
"type": "keyword"
},
"threat": {
"properties": {
"framework": {
"ignore_above": 1024,
"type": "keyword"
},
"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": {
"type": "wildcard"
},
"domain": {
"ignore_above": 1024,
"type": "keyword"
},
"email": {
"properties": {
"address": {
"ignore_above": 1024,
"type": "keyword"
}
}
},
"first_seen": {
"type": "date"
},
"geo": {
"properties": {
"city_name": {
"ignore_above": 1024,
"type": "keyword"
},
"continent_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"
},
"name": {
"ignore_above": 1024,
"type": "keyword"
},
"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"
},
"scanner_stats": {
"type": "long"
},
"sightings": {
"type": "long"
},
"type": {
"ignore_above": 1024,
"type": "keyword"
}
}
},
"tactic": {
"properties": {
"id": {
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"ignore_above": 1024,
"type": "keyword"
},
"reference": {
"ignore_above": 1024,
"type": "keyword"
}
}
},
"technique": {
"properties": {
"id": {
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"fields": {
"text": {
"norms": false,
"type": "text"
}
},
"ignore_above": 1024,
"type": "keyword"
},
"reference": {
"ignore_above": 1024,
"type": "keyword"
},
"subtechnique": {
"properties": {
"id": {
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"fields": {
"text": {
"norms": false,
"type": "text"
}
},
"ignore_above": 1024,
"type": "keyword"
},
"reference": {
"ignore_above": 1024,
"type": "keyword"
}
}
}
}
}
}
}
}
},
"settings": {
"index": {
"auto_expand_replicas": "0-1",
"mapping": {
"total_fields": {
"limit": "10000"
}
},
"number_of_replicas": "0",
"number_of_shards": "1",
"refresh_interval": "5s"
}
}
}
}

View file

@ -83,6 +83,12 @@
"prebuilt_rules_update_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless serverlessEnv",
"prebuilt_rules_update_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless qaEnv",
"prebuilt_rules_update_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/update_prebuilt_rules_package ess",
"prebuilt_rules_update_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package ess essEnvs"
"prebuilt_rules_update_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package ess essEnvs",
"rule_execution_logic:server:serverless": "npm run initialize-server:dr:default rule_execution_logic serverless",
"rule_execution_logic:runner:serverless": "npm run run-tests:dr:default rule_execution_logic serverless serverlessEnv",
"rule_execution_logic:qa:serverless": "npm run run-tests:dr:default rule_execution_logic serverless qaEnv",
"rule_execution_logic:server:ess": "npm run initialize-server:dr:default rule_execution_logic ess",
"rule_execution_logic:runner:ess": "npm run run-tests:dr:default rule_execution_logic ess essEnv"
}
}

View file

@ -36,7 +36,7 @@ import {
waitFor,
waitForRuleSuccess,
waitForAlertsToBePresent,
removeRandomValuedProperties,
removeRandomValuedPropertiesFromAlert,
} from '../../utils';
import { FtrProviderContext } from '../../../../ftr_provider_context';
@ -231,11 +231,11 @@ export default ({ getService }: FtrProviderContext) => {
expect(signalsOpen.hits.hits.length).greaterThan(0);
const hit = signalsOpen.hits.hits[0];
expect(hit._source?.kibana).to.eql(undefined);
const source = removeRandomValuedProperties(hit._source);
const source = removeRandomValuedPropertiesFromAlert(hit._source);
expect(source).to.eql({
'kibana.alert.rule.category': 'Custom Query Rule',
'kibana.alert.rule.consumer': 'siem',
'kibana.alert.rule.name': 'Signal Testing Query',
'kibana.alert.rule.name': 'Alert Testing Query',
'kibana.alert.rule.producer': 'siem',
'kibana.alert.rule.rule_type_id': 'siem.queryRule',
'kibana.space_ids': ['default'],
@ -322,7 +322,7 @@ export default ({ getService }: FtrProviderContext) => {
'kibana.alert.workflow_tags': [],
'kibana.alert.depth': 2,
'kibana.alert.reason':
'event on security-linux-1 created high alert Signal Testing Query.',
'event on security-linux-1 created high alert Alert Testing Query.',
'kibana.alert.severity': 'high',
'kibana.alert.risk_score': 1,
'kibana.alert.rule.parameters': {
@ -393,11 +393,11 @@ export default ({ getService }: FtrProviderContext) => {
expect(signalsOpen.hits.hits.length).greaterThan(0);
const hit = signalsOpen.hits.hits[0];
expect(hit._source?.kibana).to.eql(undefined);
const source = removeRandomValuedProperties(hit._source);
const source = removeRandomValuedPropertiesFromAlert(hit._source);
expect(source).to.eql({
'kibana.alert.rule.category': 'Custom Query Rule',
'kibana.alert.rule.consumer': 'siem',
'kibana.alert.rule.name': 'Signal Testing Query',
'kibana.alert.rule.name': 'Alert Testing Query',
'kibana.alert.rule.producer': 'siem',
'kibana.alert.rule.rule_type_id': 'siem.queryRule',
'kibana.space_ids': ['default'],
@ -484,7 +484,7 @@ export default ({ getService }: FtrProviderContext) => {
'kibana.alert.workflow_tags': [],
'kibana.alert.depth': 2,
'kibana.alert.reason':
'event on security-linux-1 created high alert Signal Testing Query.',
'event on security-linux-1 created high alert Alert Testing Query.',
'kibana.alert.severity': 'high',
'kibana.alert.risk_score': 1,
'kibana.alert.rule.parameters': {

View file

@ -7,12 +7,16 @@
import { FtrConfigProviderContext } from '@kbn/test';
// eslint-disable-next-line import/no-default-export
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../config.base.ts'));
const functionalConfig = await readConfigFile(
require.resolve('../../../../../config/ess/config.base.trial')
);
return {
...functionalConfig.getAll(),
testFiles: [require.resolve('.')],
testFiles: [require.resolve('..')],
junit: {
reportName: 'Detection Engine API Integration Tests - ESS - Rule Execution Logic',
},
};
}

View file

@ -0,0 +1,20 @@
/*
* 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 { createTestConfig } from '../../../../../config/serverless/config.base';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName: 'Detection Engine API Integration Tests - Serverless - Rule Execution Logic',
},
kbnTestServerArgs: [
`--xpack.securitySolution.alertIgnoreFields=${JSON.stringify([
'testing_ignored.constant',
'/testing_regex*/',
])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields"
],
});

View file

@ -27,35 +27,40 @@ import {
ALERT_ORIGINAL_EVENT_CATEGORY,
ALERT_GROUP_ID,
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import {
createRule,
deleteAllRules,
deleteAllAlerts,
getEqlRuleForSignalTesting,
getOpenSignals,
getEqlRuleForAlertTesting,
getOpenAlerts,
getPreviewAlerts,
previewRule,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
const log = getService('log');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const auditPath = dataPathBuilder.getPath('auditbeat/hosts');
describe('EQL type rules', () => {
describe('@ess @serverless EQL type rules', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load(auditPath);
await esArchiver.load(
'x-pack/test/functional/es_archives/security_solution/timestamp_override_6'
);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(auditPath);
await esArchiver.unload(
'x-pack/test/functional/es_archives/security_solution/timestamp_override_6'
);
@ -64,21 +69,21 @@ export default ({ getService }: FtrProviderContext) => {
});
// First test creates a real rule - remaining tests use preview API
it('generates a correctly formatted signal from EQL non-sequence queries', async () => {
it('generates a correctly formatted alert from EQL non-sequence queries', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).eql(1);
const fullSignal = alerts.hits.hits[0]._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = alerts.hits.hits[0]._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal).eql({
...fullSignal,
expect(fullAlert).eql({
...fullAlert,
agent: {
ephemeral_id: '0010d67a-14f7-41da-be30-489fea735967',
hostname: 'suricata-zeek-sensor-toronto',
@ -145,9 +150,9 @@ export default ({ getService }: FtrProviderContext) => {
},
},
[ALERT_REASON]:
'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.',
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
'configuration event on suricata-zeek-sensor-toronto created high alert Alert Testing Query.',
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_WORKFLOW_TAGS]: [],
[ALERT_DEPTH]: 1,
@ -167,41 +172,41 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('generates up to max_signals for non-sequence EQL queries', async () => {
const maxSignals = 200;
it('generates up to max_alerts for non-sequence EQL queries', async () => {
const maxAlerts = 200;
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
max_signals: maxSignals,
...getEqlRuleForAlertTesting(['auditbeat-*']),
max_signals: maxAlerts,
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 });
expect(previewAlerts.length).eql(maxSignals);
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxAlerts * 2 });
expect(previewAlerts.length).eql(maxAlerts);
});
it('generates max signals warning when circuit breaker is hit', async () => {
it('generates max alerts warning when circuit breaker is hit', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).contain(getMaxSignalsWarning());
expect(logs[0].warnings).contain(getMaxAlertsWarning());
});
it('uses the provided event_category_override', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
event_category_override: 'auditd.message_type',
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts.length).eql(1);
const fullSignal = previewAlerts[0]._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = previewAlerts[0]._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal).eql({
...fullSignal,
expect(fullAlert).eql({
...fullAlert,
auditd: {
data: {
audit_enabled: '1',
@ -236,9 +241,9 @@ export default ({ getService }: FtrProviderContext) => {
},
},
[ALERT_REASON]:
'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.',
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
'configuration event on suricata-zeek-sensor-toronto created high alert Alert Testing Query.',
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_DEPTH]: 1,
[ALERT_ANCESTORS]: [
@ -259,7 +264,7 @@ export default ({ getService }: FtrProviderContext) => {
it('uses the provided timestamp_field', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['fake.index.1']),
...getEqlRuleForAlertTesting(['fake.index.1']),
query: 'any where true',
timestamp_field: 'created_at',
};
@ -273,7 +278,7 @@ export default ({ getService }: FtrProviderContext) => {
it('uses the provided tiebreaker_field', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['fake.index.1']),
...getEqlRuleForAlertTesting(['fake.index.1']),
query: 'any where true',
tiebreaker_field: 'locale',
};
@ -285,9 +290,9 @@ export default ({ getService }: FtrProviderContext) => {
expect(createdAtHits).to.eql(['es', 'pt', 'ua']);
});
it('generates building block signals from EQL sequences in the expected form', async () => {
it('generates building block alerts from EQL sequences in the expected form', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'sequence by host.name [anomoly where true] [any where true]', // TODO: spelling
};
const { previewId } = await previewRule({ supertest, rule });
@ -298,13 +303,13 @@ export default ({ getService }: FtrProviderContext) => {
get(alert._source, ALERT_ORIGINAL_EVENT_CATEGORY) === 'anomoly'
);
expect(buildingBlock).not.eql(undefined);
const fullSignal = buildingBlock?._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = buildingBlock?._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal).eql({
...fullSignal,
expect(fullAlert).eql({
...fullAlert,
agent: {
ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab',
hostname: 'zeek-sensor-amsterdam',
@ -409,10 +414,10 @@ export default ({ getService }: FtrProviderContext) => {
},
},
[ALERT_REASON]:
'anomoly event with process bro, by root on zeek-sensor-amsterdam created high alert Signal Testing Query.',
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_GROUP_ID]: fullSignal[ALERT_GROUP_ID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
'anomoly event with process bro, by root on zeek-sensor-amsterdam created high alert Alert Testing Query.',
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_GROUP_ID]: fullAlert[ALERT_GROUP_ID],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_DEPTH]: 1,
[ALERT_ANCESTORS]: [
@ -431,9 +436,9 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('generates shell signals from EQL sequences in the expected form', async () => {
it('generates shell alerts from EQL sequences in the expected form', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'sequence by host.name [anomoly where true] [any where true]',
};
const { previewId } = await previewRule({ supertest, rule });
@ -480,7 +485,7 @@ export default ({ getService }: FtrProviderContext) => {
[ALERT_DEPTH]: 2,
[ALERT_GROUP_ID]: source[ALERT_GROUP_ID],
[ALERT_REASON]:
'event by root on zeek-sensor-amsterdam created high alert Signal Testing Query.',
'event by root on zeek-sensor-amsterdam created high alert Alert Testing Query.',
[ALERT_RULE_UUID]: source[ALERT_RULE_UUID],
[ALERT_ANCESTORS]: [
{
@ -513,28 +518,28 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('generates up to max_signals with an EQL rule', async () => {
const maxSignals = 200;
it('generates up to max_alerts with an EQL rule', async () => {
const maxAlerts = 200;
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'sequence by host.name [any where true] [any where true]',
max_signals: maxSignals,
max_signals: maxAlerts,
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 5 });
// For EQL rules, max_signals is the maximum number of detected sequences: each sequence has a building block
// alert for each event in the sequence, so max_signals=200 results in 400 building blocks in addition to
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxAlerts * 5 });
// For EQL rules, max_alerts is the maximum number of detected sequences: each sequence has a building block
// alert for each event in the sequence, so max_alerts=200 results in 400 building blocks in addition to
// 200 regular alerts
expect(previewAlerts.length).eql(maxSignals * 3);
const shellSignals = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 2);
expect(previewAlerts.length).eql(maxAlerts * 3);
const shellAlerts = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 2);
const buildingBlocks = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 1);
expect(shellSignals.length).eql(maxSignals);
expect(buildingBlocks.length).eql(maxSignals * 2);
expect(shellAlerts.length).eql(maxAlerts);
expect(buildingBlocks.length).eql(maxAlerts * 2);
});
it('generates signals when an index name contains special characters to encode', async () => {
it('generates alerts when an index name contains special characters to encode', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*', '<my-index-{now/d}*>']),
...getEqlRuleForAlertTesting(['auditbeat-*', '<my-index-{now/d}*>']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const { previewId } = await previewRule({ supertest, rule });
@ -544,7 +549,7 @@ export default ({ getService }: FtrProviderContext) => {
it('uses the provided filters', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'any where true',
filters: [
{
@ -599,18 +604,18 @@ export default ({ getService }: FtrProviderContext) => {
it('should be enriched with host risk score', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['auditbeat-*']),
...getEqlRuleForAlertTesting(['auditbeat-*']),
query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"',
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts.length).eql(1);
const fullSignal = previewAlerts[0]._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = previewAlerts[0]._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical');
expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(96);
expect(fullAlert?.host?.risk?.calculated_level).to.eql('Critical');
expect(fullAlert?.host?.risk?.calculated_score_norm).to.eql(96);
});
});
});

View file

@ -13,23 +13,22 @@ import { EsqlRuleCreateProps } from '@kbn/security-solution-plugin/common/api/de
import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks';
import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import {
deleteAllRules,
deleteAllAlerts,
getPreviewAlerts,
previewRule,
createRule,
getOpenSignals as getOpenAlerts,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { previewRuleWithExceptionEntries } from '../../utils/preview_rule_with_exception_entries';
import { deleteAllExceptions } from '../../../lists_api_integration/utils';
import { dataGeneratorFactory } from '../../utils/data_generator';
import { removeRandomValuedProperties } from './utils';
import { patchRule } from '../../utils/patch_rule';
getOpenAlerts,
dataGeneratorFactory,
previewRuleWithExceptionEntries,
removeRandomValuedPropertiesFromAlert,
patchRule,
} from '../../../utils';
import { deleteAllExceptions } from '../../../../../../lists_api_integration/utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
@ -47,7 +46,7 @@ export default ({ getService }: FtrProviderContext) => {
*/
const internalIdPipe = (id: string) => `| where id=="${id}"`;
describe('ES|QL rule type', () => {
describe('@ess ES|QL rule type', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant');
});
@ -84,7 +83,7 @@ export default ({ getService }: FtrProviderContext) => {
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).toBe(1);
expect(removeRandomValuedProperties(alerts.hits.hits[0]._source)).toEqual({
expect(removeRandomValuedPropertiesFromAlert(alerts.hits.hits[0]._source)).toEqual({
'kibana.alert.rule.parameters': {
description: 'Detecting root and admin users',
risk_score: 55,
@ -602,8 +601,8 @@ export default ({ getService }: FtrProviderContext) => {
});
});
describe('max signals', () => {
it('generates max signals warning when circuit breaker is exceeded', async () => {
describe('max alerts', () => {
it('generates max alerts warning when circuit breaker is exceeded', async () => {
const id = uuidv4();
const rule: EsqlRuleCreateProps = {
...getCreateEsqlRulesSchemaMock('rule-1', true),
@ -629,7 +628,7 @@ export default ({ getService }: FtrProviderContext) => {
timeframeEnd: new Date('2020-10-28T06:30:00.000Z'),
});
expect(logs[0].warnings).toEqual(expect.arrayContaining([getMaxSignalsWarning()]));
expect(logs[0].warnings).toEqual(expect.arrayContaining([getMaxAlertsWarning()]));
const previewAlerts = await getPreviewAlerts({
es,
@ -640,7 +639,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).toBe(100);
});
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
it("doesn't generate max alerts warning when circuit breaker is met but not exceeded", async () => {
const id = uuidv4();
const rule: EsqlRuleCreateProps = {
...getCreateEsqlRulesSchemaMock('rule-1', true),
@ -665,7 +664,7 @@ export default ({ getService }: FtrProviderContext) => {
rule,
timeframeEnd: new Date('2020-10-28T06:30:00.000Z'),
});
expect(logs[0].warnings).not.toEqual(expect.arrayContaining([getMaxSignalsWarning()]));
expect(logs[0].warnings).not.toEqual(expect.arrayContaining([getMaxAlertsWarning()]));
const previewAlerts = await getPreviewAlerts({
es,
@ -676,7 +675,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).toBe(100);
});
it('should work for max signals > 100', async () => {
it('should work for max alerts > 100', async () => {
const id = uuidv4();
const rule: EsqlRuleCreateProps = {
...getCreateEsqlRulesSchemaMock('rule-1', true),

View file

@ -5,11 +5,10 @@
* 2.0.
*/
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile }: FtrProviderContext): void => {
describe('detection engine api security and spaces enabled - rule execution logic', function () {
describe('Execution logic', function () {
loadTestFile(require.resolve('./eql'));
loadTestFile(require.resolve('./esql'));
loadTestFile(require.resolve('./machine_learning'));
@ -18,7 +17,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
loadTestFile(require.resolve('./threat_match'));
loadTestFile(require.resolve('./threshold'));
loadTestFile(require.resolve('./non_ecs_fields'));
loadTestFile(require.resolve('./query'));
});
};

View file

@ -24,33 +24,38 @@ import {
ALERT_DEPTH,
ALERT_ORIGINAL_TIME,
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { expect } from 'expect';
import {
createListsIndex,
deleteAllExceptions,
deleteListsIndex,
importFile,
} from '../../../lists_api_integration/utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
} from '../../../../../../lists_api_integration/utils';
import {
createRule,
deleteAllRules,
deleteAllAlerts,
executeSetupModuleRequest,
forceStartDatafeeds,
getOpenSignals,
getOpenAlerts,
getPreviewAlerts,
previewRule,
previewRuleWithExceptionEntries,
} from '../../utils';
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
const log = getService('log');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const auditPath = dataPathBuilder.getPath('auditbeat/hosts');
const siemModule = 'security_linux_v3';
const mlJobId = 'v3_linux_anomalous_network_activity';
@ -66,17 +71,17 @@ export default ({ getService }: FtrProviderContext) => {
rule_id: 'ml-rule-id',
};
describe('Machine learning type rules', () => {
describe('@ess @serverless Machine learning type rules', () => {
before(async () => {
// Order is critical here: auditbeat data must be loaded before attempting to start the ML job,
// as the job looks for certain indices on start
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load(auditPath);
await executeSetupModuleRequest({ module: siemModule, rspCode: 200, supertest });
await forceStartDatafeeds({ jobId: mlJobId, rspCode: 200, supertest });
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/anomalies');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(auditPath);
await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/anomalies');
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
@ -85,11 +90,11 @@ export default ({ getService }: FtrProviderContext) => {
// First test creates a real rule - remaining tests use preview API
it('should create 1 alert from ML rule when record meets anomaly_threshold', async () => {
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).toBe(1);
const signal = alerts.hits.hits[0];
const alert = alerts.hits.hits[0];
expect(signal._source).toEqual(
expect(alert._source).toEqual(
expect.objectContaining({
'@timestamp': expect.any(String),
[ALERT_RULE_EXECUTION_UUID]: expect.any(String),
@ -160,23 +165,23 @@ export default ({ getService }: FtrProviderContext) => {
);
});
it('generates max signals warning when circuit breaker is exceeded', async () => {
it('@skipInQA generates max alerts warning when circuit breaker is exceeded', async () => {
const { logs } = await previewRule({
supertest,
rule: { ...rule, anomaly_threshold: 1, max_signals: 5 }, // This threshold generates 10 alerts with the current esArchive
});
expect(logs[0].warnings).toContain(getMaxSignalsWarning());
expect(logs[0].warnings).toContain(getMaxAlertsWarning());
});
it("doesn't generate max signals warning when circuit breaker is met, but not exceeded", async () => {
it("doesn't generate max alerts warning when circuit breaker is met, but not exceeded", async () => {
const { logs } = await previewRule({
supertest,
rule: { ...rule, anomaly_threshold: 1, max_signals: 10 },
});
expect(logs[0].warnings).not.toContain(getMaxSignalsWarning());
expect(logs[0].warnings).not.toContain(getMaxAlertsWarning());
});
it('should create 7 alerts from ML rule when records meet anomaly_threshold', async () => {
it('@skipInQA should create 7 alerts from ML rule when records meet anomaly_threshold', async () => {
const { previewId } = await previewRule({
supertest,
rule: { ...rule, anomaly_threshold: 20 },
@ -189,7 +194,7 @@ export default ({ getService }: FtrProviderContext) => {
afterEach(async () => {
await deleteAllExceptions(supertest, log);
});
it('generates no signals when an exception is added for an ML rule', async () => {
it('generates no alerts when an exception is added for an ML rule', async () => {
const { previewId } = await previewRuleWithExceptionEntries({
supertest,
log,
@ -220,7 +225,7 @@ export default ({ getService }: FtrProviderContext) => {
await deleteAllExceptions(supertest, log);
});
it('generates no signals when a value list exception is added for an ML rule', async () => {
it('generates no alerts when a value list exception is added for an ML rule', async () => {
const valueListId = 'value-list-id';
await importFile(supertest, log, 'keyword', ['mothra'], valueListId);
const { previewId } = await previewRuleWithExceptionEntries({
@ -255,14 +260,14 @@ export default ({ getService }: FtrProviderContext) => {
await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks');
});
it('should be enriched with host risk score', async () => {
it('@skipInQA should be enriched with host risk score', async () => {
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts.length).toBe(1);
const fullSignal = previewAlerts[0]._source;
const fullAlert = previewAlerts[0]._source;
expect(fullSignal?.host?.risk?.calculated_level).toBe('Low');
expect(fullSignal?.host?.risk?.calculated_score_norm).toBe(1);
expect(fullAlert?.host?.risk?.calculated_level).toBe('Low');
expect(fullAlert?.host?.risk?.calculated_score_norm).toBe(1);
});
});
});

View file

@ -12,26 +12,25 @@ import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/ap
import { orderBy } from 'lodash';
import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import {
createRule,
deleteAllRules,
deleteAllAlerts,
getOpenSignals,
getOpenAlerts,
getPreviewAlerts,
previewRule,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { previewRuleWithExceptionEntries } from '../../utils/preview_rule_with_exception_entries';
import { deleteAllExceptions } from '../../../lists_api_integration/utils';
import { dataGeneratorFactory } from '../../utils/data_generator';
import { removeRandomValuedProperties } from './utils';
dataGeneratorFactory,
previewRuleWithExceptionEntries,
removeRandomValuedPropertiesFromAlert,
} from '../../../utils';
import { deleteAllExceptions } from '../../../../../../lists_api_integration/utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
const historicalWindowStart = '2022-10-13T05:00:04.000Z';
const ruleExecutionStart = '2022-10-19T05:00:04.000Z';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
@ -42,7 +41,12 @@ export default ({ getService }: FtrProviderContext) => {
index: 'new_terms',
log,
});
// TODO: add a new service
const config = getService('config');
const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');
/**
* indexes 2 sets of documents:
* - documents in historical window
@ -72,14 +76,14 @@ export default ({ getService }: FtrProviderContext) => {
return testId;
};
describe('New terms type rules', () => {
describe('@ess @serverless New terms type rules', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load(path);
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/new_terms');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(path);
await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/new_terms');
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
@ -99,10 +103,10 @@ export default ({ getService }: FtrProviderContext) => {
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).eql(1);
expect(removeRandomValuedProperties(alerts.hits.hits[0]._source)).eql({
expect(removeRandomValuedPropertiesFromAlert(alerts.hits.hits[0]._source)).eql({
'kibana.alert.new_terms': ['zeek-newyork-sha-aa8df15'],
'kibana.alert.rule.category': 'New Terms Rule',
'kibana.alert.rule.consumer': 'siem',
@ -196,7 +200,7 @@ export default ({ getService }: FtrProviderContext) => {
},
'kibana.alert.rule.actions': [],
'kibana.alert.rule.author': [],
'kibana.alert.rule.created_by': 'elastic',
'kibana.alert.rule.created_by': ELASTICSEARCH_USERNAME,
'kibana.alert.rule.description': 'Detecting root and admin users',
'kibana.alert.rule.enabled': true,
'kibana.alert.rule.exceptions_list': [],
@ -214,7 +218,7 @@ export default ({ getService }: FtrProviderContext) => {
'kibana.alert.rule.threat': [],
'kibana.alert.rule.to': 'now',
'kibana.alert.rule.type': 'new_terms',
'kibana.alert.rule.updated_by': 'elastic',
'kibana.alert.rule.updated_by': ELASTICSEARCH_USERNAME,
'kibana.alert.rule.version': 1,
'kibana.alert.rule.risk_score': 55,
'kibana.alert.rule.severity': 'high',
@ -229,7 +233,7 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('generates max signals warning when circuit breaker is exceeded', async () => {
it('generates max alerts warning when circuit breaker is exceeded', async () => {
const rule: NewTermsRuleCreateProps = {
...getCreateNewTermsRulesSchemaMock('rule-1', true),
new_terms_fields: ['process.pid'],
@ -239,10 +243,10 @@ export default ({ getService }: FtrProviderContext) => {
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).contain(getMaxSignalsWarning());
expect(logs[0].warnings).contain(getMaxAlertsWarning());
});
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
it("doesn't generate max alerts warning when circuit breaker is met but not exceeded", async () => {
const rule: NewTermsRuleCreateProps = {
...getCreateNewTermsRulesSchemaMock('rule-1', true),
new_terms_fields: ['host.ip'],
@ -252,7 +256,7 @@ export default ({ getService }: FtrProviderContext) => {
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).not.contain(getMaxSignalsWarning());
expect(logs[0].warnings).not.contain(getMaxAlertsWarning());
});
it('should generate 3 alerts when 1 document has 3 new values', async () => {
@ -990,21 +994,21 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('should work for max signals > 100', async () => {
const maxSignals = 200;
it('should work for max alerts > 100', async () => {
const maxAlerts = 200;
const rule: NewTermsRuleCreateProps = {
...getCreateNewTermsRulesSchemaMock('rule-1', true),
new_terms_fields: ['process.pid'],
from: '2018-02-19T20:42:00.000Z',
// Set the history_window_start close to 'from' so we should alert on all terms in the time range
history_window_start: '2018-02-19T20:41:59.000Z',
max_signals: maxSignals,
max_signals: maxAlerts,
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 });
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxAlerts * 2 });
expect(previewAlerts.length).eql(maxSignals);
expect(previewAlerts.length).eql(maxAlerts);
const processPids = previewAlerts
.map((signal) => signal._source?.['kibana.alert.new_terms'])
.sort();

View file

@ -10,18 +10,18 @@ import {
deleteAllRules,
deleteAllAlerts,
getPreviewAlerts,
getRuleForSignalTesting,
getRuleForAlertTesting,
previewRule,
} from '../../utils';
import { dataGeneratorFactory, enhanceDocument } from '../../utils/data_generator';
import { FtrProviderContext } from '../../common/ftr_provider_context';
dataGeneratorFactory,
enhanceDocument,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
const getQueryRule = (docIdToQuery: string) => ({
...getRuleForSignalTesting(['ecs_non_compliant']),
...getRuleForAlertTesting(['ecs_non_compliant']),
query: `id: "${docIdToQuery}"`,
});
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
@ -56,7 +56,7 @@ export default ({ getService }: FtrProviderContext) => {
};
};
describe('Non ECS fields in alert document source', () => {
describe('@ess @serverless Non ECS fields in alert document source', () => {
before(async () => {
await esArchiver.load(
'x-pack/test/functional/es_archives/security_solution/ecs_non_compliant'

View file

@ -42,29 +42,32 @@ import {
import {
DETECTION_ENGINE_RULES_BULK_ACTION,
DETECTION_ENGINE_RULES_URL,
DETECTION_ENGINE_SIGNALS_STATUS_URL,
DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL,
} from '@kbn/security-solution-plugin/common/constants';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { deleteAllExceptions } from '../../../lists_api_integration/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import moment from 'moment';
import { deleteAllExceptions } from '../../../../../../lists_api_integration/utils';
import {
createExceptionList,
createExceptionListItem,
createRule,
deleteAllRules,
deleteAllAlerts,
getOpenSignals,
getOpenAlerts,
getPreviewAlerts,
getRuleForSignalTesting,
getRuleForAlertTesting,
getSimpleRule,
previewRule,
setSignalStatus,
setAlertStatus,
getRuleSOById,
patchRule,
createRuleThroughAlertingEndpoint,
getRuleSavedObjectWithLegacyInvestigationFields,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { dataGeneratorFactory } from '../../utils/data_generator';
import { patchRule } from '../../utils/patch_rule';
dataGeneratorFactory,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
/**
* Specific _id to use for some of the tests. If the archiver changes and you see errors
@ -75,22 +78,29 @@ const ID = 'BhbXBmkBR346wHgn4PeZ';
/**
* Test coverage:
* [x] - Happy path generating 1 alert
* [x] - Rule type respects max signals
* [x] - Rule type respects max alerts
* [x] - Alerts on alerts
*/
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
const log = getService('log');
const esDeleteAllIndices = getService('esDeleteAllIndices');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const auditbeatPath = dataPathBuilder.getPath('auditbeat/hosts');
describe('Query type rules', () => {
describe('@ess @serverless Query type rules', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0');
await esArchiver.load(auditbeatPath);
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/8.8.0', {
useCreate: true,
docsOnly: true,
});
await esArchiver.load('x-pack/test/functional/es_archives/signals/severity_risk_overrides');
});
@ -99,8 +109,7 @@ export default ({ getService }: FtrProviderContext) => {
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0');
await esArchiver.unload(auditbeatPath);
await esArchiver.unload('x-pack/test/functional/es_archives/signals/severity_risk_overrides');
await deleteAllAlerts(supertest, log, es, ['.preview.alerts-security.alerts-*']);
await deleteAllRules(supertest, log);
@ -109,48 +118,48 @@ export default ({ getService }: FtrProviderContext) => {
// First test creates a real rule - most remaining tests use preview API
it('should have the specific audit record for _id or none of these tests below will pass', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).greaterThan(0);
expect(alerts.hits.hits[0]._source?.['kibana.alert.ancestors'][0].id).eql(ID);
});
it('generates max signals warning when circuit breaker is hit', async () => {
it('generates max alerts warning when circuit breaker is hit', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).contain(getMaxSignalsWarning());
expect(logs[0].warnings).contain(getMaxAlertsWarning());
});
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
it("doesn't generate max alerts warning when circuit breaker is met but not exceeded", async () => {
const rule = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: 'process.executable: "/usr/bin/sudo"',
max_signals: 10,
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).not.contain(getMaxSignalsWarning());
expect(logs[0].warnings).not.contain(getMaxAlertsWarning());
});
it('should abide by max_signals > 100', async () => {
const maxSignals = 200;
const maxAlerts = 200;
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
max_signals: maxSignals,
...getRuleForAlertTesting(['auditbeat-*']),
max_signals: maxAlerts,
};
const { previewId } = await previewRule({ supertest, rule });
// Search for 2x max_signals to make sure we aren't making more than max_signals
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 });
expect(previewAlerts.length).equal(maxSignals);
const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxAlerts * 2 });
expect(previewAlerts.length).equal(maxAlerts);
});
it('should have recorded the rule_id within the signal', async () => {
it('should have recorded the rule_id within the alert', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
const { previewId } = await previewRule({ supertest, rule });
@ -158,17 +167,17 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts[0]._source?.[ALERT_RULE_RULE_ID]).eql(getSimpleRule().rule_id);
});
it('should query and get back expected signal structure using a basic KQL query', async () => {
it('should query and get back expected alert structure using a basic KQL query', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
const signal = previewAlerts[0]._source;
const alert = previewAlerts[0]._source;
expect(signal).eql({
...signal,
expect(alert).eql({
...alert,
[ALERT_ANCESTORS]: [
{
id: 'BhbXBmkBR346wHgn4PeZ',
@ -189,11 +198,11 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('should query and get back expected signal structure when it is a signal on a signal', async () => {
const alertId = '30a75fe46d3dbdfab55982036f77a8d60e2d1112e96f277c3b8c22f9bb57817a';
it('should query and get back expected alert structure when it is a alert on a alert', async () => {
const alertId = 'eabbdefc23da981f2b74ab58b82622a97bb9878caa11bc914e2adfacc94780f1';
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting([`.alerts-security.alerts-default*`]),
rule_id: 'signal-on-signal',
...getRuleForAlertTesting([`.alerts-security.alerts-default*`]),
rule_id: 'alert-on-alert',
query: `_id:${alertId}`,
};
@ -202,43 +211,44 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).to.eql(1);
const signal = previewAlerts[0]._source;
const alert = previewAlerts[0]._source;
if (!signal) {
return expect(signal).to.be.ok();
if (!alert) {
return expect(alert).to.be.ok();
}
const date = moment();
const formattedDate = date.format('YYYY.MM.DD');
const alertAncestorIndex = isServerless
? `.ds-.alerts-security.alerts-default-${formattedDate}-000001`
: '.internal.alerts-security.alerts-default-000001';
expect(alert[ALERT_ANCESTORS]).eql([
{
id: 'vT9cwocBh3b8EMpD8lsi',
type: 'event',
index: '.ds-logs-endpoint.alerts-default-2023.04.27-000001',
depth: 0,
},
{
rule: '7015a3e2-e4ea-11ed-8c11-49608884878f',
id: alertId,
type: 'signal',
index: alertAncestorIndex,
depth: 1,
},
]);
expect(alert[ALERT_WORKFLOW_STATUS]).eql('open');
expect(alert[ALERT_DEPTH]).eql(2);
expect(signal).eql({
...signal,
[ALERT_ANCESTORS]: [
{
id: 'ahEToH8BK09aFtXZFVMq',
type: 'event',
index: 'events-index-000001',
depth: 0,
},
{
rule: '031d5c00-a72f-11ec-a8a3-7b1c8077fc3e',
id: '30a75fe46d3dbdfab55982036f77a8d60e2d1112e96f277c3b8c22f9bb57817a',
type: 'signal',
index: '.internal.alerts-security.alerts-default-000001',
depth: 1,
},
],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_DEPTH]: 2,
[ALERT_ORIGINAL_TIME]: '2022-03-19T02:48:12.634Z',
...flattenWithPrefix(ALERT_ORIGINAL_EVENT, {
agent_id_status: 'verified',
ingested: '2022-03-19T02:47:57.376Z',
dataset: 'elastic_agent.filebeat',
}),
});
expect(alert[ALERT_ORIGINAL_TIME]).eql('2023-04-27T11:03:57.906Z');
expect(alert[`${ALERT_ORIGINAL_EVENT}.agent_id_status`]).eql('auth_metadata_missing');
expect(alert[`${ALERT_ORIGINAL_EVENT}.ingested`]).eql('2023-04-27T10:58:03Z');
expect(alert[`${ALERT_ORIGINAL_EVENT}.dataset`]).eql('endpoint');
expect(alert[`${ALERT_ORIGINAL_EVENT}.ingested`]).eql('2023-04-27T10:58:03Z');
});
it('should not have risk score fields without risk indices', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
const { previewId } = await previewRule({ supertest, rule });
@ -258,7 +268,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should have host and user risk score fields', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
const { previewId } = await previewRule({ supertest, rule });
@ -273,11 +283,11 @@ export default ({ getService }: FtrProviderContext) => {
/**
* Here we test the functionality of Severity and Risk Score overrides (also called "mappings"
* in the code). If the rule specifies a mapping, then the final Severity or Risk Score
* value of the signal will be taken from the mapped field of the source event.
* value of the alert will be taken from the mapped field of the source event.
*/
it('should get default severity and risk score if there is no mapping', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['signal_overrides']),
...getRuleForAlertTesting(['signal_overrides']),
severity: 'medium',
risk_score: 75,
};
@ -297,7 +307,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should get overridden severity if the rule has a mapping for it', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['signal_overrides']),
...getRuleForAlertTesting(['signal_overrides']),
severity: 'medium',
severity_mapping: [
{ field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' },
@ -334,7 +344,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should get overridden risk score if the rule has a mapping for it', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['signal_overrides']),
...getRuleForAlertTesting(['signal_overrides']),
severity: 'medium',
risk_score: 75,
risk_score_mapping: [
@ -369,7 +379,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should get overridden severity and risk score if the rule has both mappings', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['signal_overrides']),
...getRuleForAlertTesting(['signal_overrides']),
severity: 'medium',
severity_mapping: [
{ field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' },
@ -409,26 +419,26 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('should generate signals with name_override field', async () => {
it('should generate alerts with name_override field', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `event.action:boot`,
rule_name_override: 'event.action',
};
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
const fullSignal = previewAlerts[0];
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = previewAlerts[0];
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(previewAlerts[0]._source?.['kibana.alert.rule.name']).to.eql('boot');
});
it('should not generate duplicate signals', async () => {
it('should not generate duplicate alerts', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
@ -448,7 +458,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should generate only 1 alert per host name when grouping by host name', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: "host-0"`,
alert_suppression: {
group_by: ['host.name'],
@ -481,7 +491,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should generate multiple alerts when multiple host names are found', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: *`,
alert_suppression: {
group_by: ['host.name'],
@ -521,7 +531,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should generate alerts when using multiple group by fields', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: *`,
alert_suppression: {
group_by: ['host.name', 'source.ip'],
@ -564,7 +574,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should not count documents that were covered by previous alerts', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: *`,
alert_suppression: {
group_by: ['host.name', 'source.ip'],
@ -632,7 +642,7 @@ export default ({ getService }: FtrProviderContext) => {
// so we expect 2 groups to be created from the single document
it('should generate multiple alerts for a single doc in multiple groups', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `*:*`,
alert_suppression: {
group_by: ['destination.ip'],
@ -689,7 +699,7 @@ export default ({ getService }: FtrProviderContext) => {
// The last alert, with null for destination.ip, should be found by the first rule run but not duplicated
// by the second run.
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `*:*`,
alert_suppression: {
group_by: ['destination.ip'],
@ -760,7 +770,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexListOfDocuments([firstDocument, firstDocument]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
rule_id: 'rule-2',
query: `id:${id}`,
alert_suppression: {
@ -772,7 +782,7 @@ export default ({ getService }: FtrProviderContext) => {
},
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).eql(1);
expect(alerts.hits.hits[0]._source).to.eql({
...alerts.hits.hits[0]._source,
@ -802,7 +812,7 @@ export default ({ getService }: FtrProviderContext) => {
await patchRule(supertest, log, { id: createdRule.id, enabled: false });
await patchRule(supertest, log, { id: createdRule.id, enabled: true });
const afterTimestamp = new Date();
const secondAlerts = await getOpenSignals(
const secondAlerts = await getOpenAlerts(
supertest,
log,
es,
@ -841,7 +851,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexListOfDocuments([firstDocument, firstDocument]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
rule_id: 'rule-2',
query: `id:${id}`,
alert_suppression: {
@ -853,15 +863,15 @@ export default ({ getService }: FtrProviderContext) => {
},
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
// Close the alert. Subsequent rule executions should ignore this closed alert
// for suppression purposes.
const alertIds = alerts.hits.hits.map((alert) => alert._id);
await supertest
.post(DETECTION_ENGINE_SIGNALS_STATUS_URL)
.post(DETECTION_ENGINE_ALERTS_STATUS_URL)
.set('kbn-xsrf', 'true')
.send(setSignalStatus({ signalIds: alertIds, status: 'closed' }))
.send(setAlertStatus({ alertIds, status: 'closed' }))
.expect(200);
const secondTimestamp = new Date().toISOString();
@ -878,7 +888,7 @@ export default ({ getService }: FtrProviderContext) => {
await patchRule(supertest, log, { id: createdRule.id, enabled: false });
await patchRule(supertest, log, { id: createdRule.id, enabled: true });
const afterTimestamp = new Date();
const secondAlerts = await getOpenSignals(
const secondAlerts = await getOpenAlerts(
supertest,
log,
es,
@ -922,7 +932,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should generate an alert per rule run when duration is less than rule interval', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: "host-0"`,
alert_suppression: {
group_by: ['host.name'],
@ -981,7 +991,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should update an existing alert in the time window', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: "host-0"`,
alert_suppression: {
group_by: ['host.name'],
@ -1025,7 +1035,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should update the correct alerts based on group_by field-value pair', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: *`,
alert_suppression: {
group_by: ['host.name'],
@ -1099,7 +1109,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should update the correct alerts based on group_by field-value pair even when value is null', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: *`,
alert_suppression: {
group_by: ['destination.ip'], // Only 1 document populates destination.ip
@ -1162,7 +1172,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexListOfDocuments([docWithoutOverride, docWithOverride]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1226,7 +1236,7 @@ export default ({ getService }: FtrProviderContext) => {
);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1313,7 +1323,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexListOfDocuments([firstDoc, secondDoc, thirdDoc]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1383,7 +1393,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should be enriched with host risk score', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['suppression-data']),
...getRuleForAlertTesting(['suppression-data']),
query: `host.name: "host-0"`,
alert_suppression: {
group_by: ['host.name'],
@ -1474,7 +1484,7 @@ export default ({ getService }: FtrProviderContext) => {
]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1545,7 +1555,7 @@ export default ({ getService }: FtrProviderContext) => {
]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1600,7 +1610,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexListOfDocuments([firstDoc, firstDoc]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1654,7 +1664,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexListOfDocuments([firstDoc, firstDoc]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1752,7 +1762,7 @@ export default ({ getService }: FtrProviderContext) => {
]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name', 'agent.version'],
@ -1831,7 +1841,7 @@ export default ({ getService }: FtrProviderContext) => {
]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name', 'agent.version'],
@ -1898,7 +1908,7 @@ export default ({ getService }: FtrProviderContext) => {
});
it('should create suppressed alerts for single host.name when rule configure with suppress', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -1965,7 +1975,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should create unsuppressed alerts for single host.name', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
@ -2076,7 +2086,7 @@ export default ({ getService }: FtrProviderContext) => {
});
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`,
exceptions_list: [{ id, list_id: listId, type, namespace_type: namespaceType }],
};
@ -2119,7 +2129,7 @@ export default ({ getService }: FtrProviderContext) => {
});
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`,
exceptions_list: [{ id, list_id: listId, type, namespace_type: namespaceType }],
};
@ -2157,7 +2167,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexEnhancedDocuments({ documents: [firstDoc, firstDoc, secondDoc], id });
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id} AND agent.n*: test-1`,
from: 'now-1h',
interval: '1h',
@ -2188,7 +2198,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexEnhancedDocuments({ documents: [firstDoc, firstDoc, secondDoc], id });
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id} AND NOT agent.na*: "test-1"`,
from: 'now-1h',
interval: '1h',
@ -2215,7 +2225,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexEnhancedDocuments({ documents: [firstDoc, secondDoc, thirdDoc], id });
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id} AND agent*: "test-1"`,
from: 'now-1h',
interval: '1h',
@ -2249,7 +2259,7 @@ export default ({ getService }: FtrProviderContext) => {
await indexEnhancedDocuments({ documents: [firstDoc, secondDoc, thirdDoc], id });
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
...getRuleForAlertTesting(['ecs_compliant']),
query: `id:${id} AND agent.\\*: test-1`,
from: 'now-1h',
interval: '1h',
@ -2276,7 +2286,8 @@ export default ({ getService }: FtrProviderContext) => {
});
});
describe('legacy investigation_fields', () => {
// TODO: Ask YARA
describe('@brokenInServerless legacy investigation_fields', () => {
let ruleWithLegacyInvestigationField: Rule<BaseRuleParams>;
beforeEach(async () => {
@ -2317,7 +2328,7 @@ export default ({ getService }: FtrProviderContext) => {
.set('elastic-api-version', '2023-10-31')
.expect(200);
const alertsAfterEnable = await getOpenSignals(supertest, log, es, ruleBody, 'succeeded');
const alertsAfterEnable = await getOpenAlerts(supertest, log, es, ruleBody, 'succeeded');
expect(alertsAfterEnable.hits.hits.length > 0).eql(true);
});
});

View file

@ -20,10 +20,11 @@ import {
createRule,
deleteAllRules,
deleteAllAlerts,
getOpenSignals,
getRuleForSignalTesting,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
getOpenAlerts,
getRuleForAlertTesting,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
/**
* Specific _id to use for some of the tests. If the archiver changes and you see errors
@ -31,37 +32,41 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
*/
const ID = 'BhbXBmkBR346wHgn4PeZ';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
const log = getService('log');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');
describe('Saved query type rules', () => {
describe('@ess @serverless Saved query type rules', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load(path);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(path);
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
});
// First test creates a real rule - remaining tests use preview API
it('should query and get back expected signal structure using a saved query rule', async () => {
it('should query and get back expected alert structure using a saved query rule', async () => {
const rule: SavedQueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
type: 'saved_query',
query: `_id:${ID}`,
saved_id: 'doesnt-exist',
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const signal = alerts.hits.hits[0]._source;
expect(signal).eql({
...signal,
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
const alert = alerts.hits.hits[0]._source;
expect(alert).eql({
...alert,
[ALERT_ANCESTORS]: [
{
id: 'BhbXBmkBR346wHgn4PeZ',

View file

@ -35,16 +35,18 @@ import {
ALERT_ORIGINAL_TIME,
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import {
previewRule,
getOpenSignals,
getOpenAlerts,
getPreviewAlerts,
deleteAllAlerts,
deleteAllRules,
createRule,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
const format = (value: unknown): string => JSON.stringify(value, null, 2);
// Asserts that each expected value is included in the subject, independent of
@ -108,8 +110,8 @@ const createThreatMatchRule = ({
threat_index,
threat_mapping,
threat_filters: [],
threat_indicator_path,
...override,
threat_indicator_path,
});
function alertsAreTheSame(alertsA: any[], alertsB: any[]): void {
@ -130,6 +132,7 @@ function alertsAreTheSame(alertsA: any[], alertsB: any[]): void {
'kibana.alert.start',
'kibana.alert.reason',
'kibana.alert.uuid',
'kibana.alert.url',
]);
};
@ -138,37 +141,40 @@ function alertsAreTheSame(alertsA: any[], alertsB: any[]): void {
expect(sort(alertsA.map(mapAlert))).to.eql(sort(alertsB.map(mapAlert)));
}
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const es = getService('es');
const log = getService('log');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const audibeatHostsPath = dataPathBuilder.getPath('auditbeat/hosts');
const threatIntelPath = dataPathBuilder.getPath('filebeat/threat_intel');
/**
* Specific api integration tests for threat matching rule type
*/
// FLAKY: https://github.com/elastic/kibana/issues/155304
describe('Threat match type rules', () => {
describe('@ess @serverless 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');
await esArchiver.load(audibeatHostsPath);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(audibeatHostsPath);
await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
});
// First 2 test creates a real rule - remaining tests use preview API
it('should be able to execute and get all signals when doing a specific query (terms query)', async () => {
it('should be able to execute and get all alerts when doing a specific query (terms query)', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule();
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(
const alerts = await getOpenAlerts(
supertest,
log,
es,
@ -178,16 +184,15 @@ export default ({ getService }: FtrProviderContext) => {
);
expect(alerts.hits.hits.length).equal(88);
const fullSource = alerts.hits.hits.find(
(signal) =>
(signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
(alert) => (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
);
const fullSignal = fullSource?._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = fullSource?._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal).eql({
...fullSignal,
'@timestamp': fullSignal['@timestamp'],
expect(fullAlert).eql({
...fullAlert,
'@timestamp': fullAlert['@timestamp'],
agent: {
ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab',
hostname: 'zeek-sensor-amsterdam',
@ -281,25 +286,25 @@ export default ({ getService }: FtrProviderContext) => {
[ALERT_ORIGINAL_EVENT_ACTION]: 'error',
[ALERT_ORIGINAL_EVENT_CATEGORY]: 'user-login',
[ALERT_ORIGINAL_EVENT_MODULE]: 'auditd',
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_REASON]:
'user-login event with source 46.101.47.213 by root on zeek-sensor-amsterdam created high alert Query with a rule id.',
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_STATUS]: 'active',
[ALERT_UUID]: fullSignal[ALERT_UUID],
[ALERT_UUID]: fullAlert[ALERT_UUID],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_WORKFLOW_TAGS]: [],
[SPACE_IDS]: ['default'],
[VERSION]: fullSignal[VERSION],
[VERSION]: fullAlert[VERSION],
threat: {
enrichments: get(fullSignal, 'threat.enrichments'),
enrichments: get(fullAlert, 'threat.enrichments'),
},
...flattenWithPrefix(ALERT_RULE_NAMESPACE, {
actions: [],
author: [],
category: 'Indicator Match Rule',
consumer: 'siem',
created_by: 'elastic',
created_by: ELASTICSEARCH_USERNAME,
description: 'Detecting root and admin users',
enabled: true,
exceptions_list: [],
@ -320,14 +325,14 @@ export default ({ getService }: FtrProviderContext) => {
threat: [],
to: 'now',
type: 'threat_match',
updated_at: fullSignal[ALERT_RULE_UPDATED_AT],
updated_by: 'elastic',
uuid: fullSignal[ALERT_RULE_UUID],
updated_at: fullAlert[ALERT_RULE_UPDATED_AT],
updated_by: ELASTICSEARCH_USERNAME,
uuid: fullAlert[ALERT_RULE_UUID],
version: 1,
}),
});
});
it('should be able to execute and get all signals when doing a specific query (match query)', async () => {
it('should be able to execute and get all alerts when doing a specific query (match query)', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
threat_mapping: [
// We match host.name against host.name
@ -349,7 +354,7 @@ export default ({ getService }: FtrProviderContext) => {
});
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(
const alerts = await getOpenAlerts(
supertest,
log,
es,
@ -359,16 +364,15 @@ export default ({ getService }: FtrProviderContext) => {
);
expect(alerts.hits.hits.length).equal(88);
const fullSource = alerts.hits.hits.find(
(signal) =>
(signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
(alert) => (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
);
const fullSignal = fullSource?._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = fullSource?._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal).eql({
...fullSignal,
'@timestamp': fullSignal['@timestamp'],
expect(fullAlert).eql({
...fullAlert,
'@timestamp': fullAlert['@timestamp'],
agent: {
ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab',
hostname: 'zeek-sensor-amsterdam',
@ -462,24 +466,24 @@ export default ({ getService }: FtrProviderContext) => {
[ALERT_ORIGINAL_EVENT_ACTION]: 'error',
[ALERT_ORIGINAL_EVENT_CATEGORY]: 'user-login',
[ALERT_ORIGINAL_EVENT_MODULE]: 'auditd',
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_REASON]:
'user-login event with source 46.101.47.213 by root on zeek-sensor-amsterdam created high alert Query with a rule id.',
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_STATUS]: 'active',
[ALERT_UUID]: fullSignal[ALERT_UUID],
[ALERT_UUID]: fullAlert[ALERT_UUID],
[ALERT_WORKFLOW_STATUS]: 'open',
[SPACE_IDS]: ['default'],
[VERSION]: fullSignal[VERSION],
[VERSION]: fullAlert[VERSION],
threat: {
enrichments: get(fullSignal, 'threat.enrichments'),
enrichments: get(fullAlert, 'threat.enrichments'),
},
...flattenWithPrefix(ALERT_RULE_NAMESPACE, {
actions: [],
author: [],
category: 'Indicator Match Rule',
consumer: 'siem',
created_by: 'elastic',
created_by: ELASTICSEARCH_USERNAME,
description: 'Detecting root and admin users',
enabled: true,
exceptions_list: [],
@ -500,18 +504,18 @@ export default ({ getService }: FtrProviderContext) => {
threat: [],
to: 'now',
type: 'threat_match',
updated_at: fullSignal[ALERT_RULE_UPDATED_AT],
updated_by: 'elastic',
uuid: fullSignal[ALERT_RULE_UUID],
updated_at: fullAlert[ALERT_RULE_UPDATED_AT],
updated_by: ELASTICSEARCH_USERNAME,
uuid: fullAlert[ALERT_RULE_UUID],
version: 1,
}),
});
});
it('generates max signals warning when circuit breaker is hit', async () => {
it('generates max alerts warning when circuit breaker is hit', async () => {
const rule: ThreatMatchRuleCreateProps = { ...createThreatMatchRule(), max_signals: 87 }; // Query generates 88 alerts with current esArchive
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).contain(getMaxSignalsWarning());
expect(logs[0].warnings).contain(getMaxAlertsWarning());
});
it('terms and match should have the same alerts with pagination', async () => {
@ -552,7 +556,7 @@ export default ({ getService }: FtrProviderContext) => {
const createdRuleTerm = await createRule(supertest, log, termRule);
const createdRuleMatch = await createRule(supertest, log, matchRule);
const alertsTerm = await getOpenSignals(
const alertsTerm = await getOpenAlerts(
supertest,
log,
es,
@ -560,7 +564,7 @@ export default ({ getService }: FtrProviderContext) => {
RuleExecutionStatusEnum.succeeded,
100
);
const alertsMatch = await getOpenSignals(
const alertsMatch = await getOpenAlerts(
supertest,
log,
es,
@ -593,7 +597,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).equal(0);
});
it('should return 0 signals when using an AND and one of the clauses does not have data', async () => {
it('should return 0 alerts when using an AND and one of the clauses does not have data', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
threat_mapping: [
{
@ -618,7 +622,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).equal(0);
});
it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => {
it('should return 0 alerts when using an AND and one of the clauses has a made up value that does not exist', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
threat_mapping: [
{
@ -661,14 +665,14 @@ export default ({ getService }: FtrProviderContext) => {
describe('indicator enrichment: threat-first search', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel');
await esArchiver.load(threatIntelPath);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel');
await esArchiver.unload(threatIntelPath);
});
it('enriches signals with the single indicator that matched', async () => {
it('enriches alerts with the single indicator that matched', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
threat_mapping: [
{
@ -744,9 +748,9 @@ export default ({ getService }: FtrProviderContext) => {
]);
});
it('enriches signals with multiple indicators if several matched', async () => {
it('enriches alerts with multiple indicators if several matched', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match
query: 'NOT source.port:35326', // specify query to have alerts more than treat indicators, but only 1 will match
threat_query: 'threat.indicator.ip: *',
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
threat_mapping: [
@ -811,7 +815,7 @@ export default ({ getService }: FtrProviderContext) => {
it('adds a single indicator that matched multiple fields', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match
query: 'NOT source.port:35326', // specify query to have alerts more than treat indicators, but only 1 will match
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: [
@ -907,7 +911,7 @@ export default ({ getService }: FtrProviderContext) => {
]);
});
it('generates multiple signals with multiple matches', async () => {
it('generates multiple alerts with multiple matches', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
threat_query: '*:*',
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
@ -1032,7 +1036,7 @@ export default ({ getService }: FtrProviderContext) => {
});
// https://github.com/elastic/kibana/issues/149920
// generates same number of alerts similarly to "enriches signals with the single indicator that matches" test
// generates same number of alerts similarly to "enriches alerts with the single indicator that matches" test
it('generates alerts with single match if queries contain field path wildcards', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
// still matches all documents as default *:*
@ -1060,14 +1064,14 @@ export default ({ getService }: FtrProviderContext) => {
describe('indicator enrichment: event-first search', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel');
await esArchiver.load(threatIntelPath);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel');
await esArchiver.unload(threatIntelPath);
});
it('enriches signals with the single indicator that matched', async () => {
it('enriches alerts with the single indicator that matched', async () => {
const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
query: 'destination.ip:159.89.119.67',
threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain
@ -1168,7 +1172,7 @@ export default ({ getService }: FtrProviderContext) => {
alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts);
});
it('enriches signals with multiple indicators if several matched', async () => {
it('enriches alerts with multiple indicators if several matched', async () => {
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: *',
@ -1408,7 +1412,7 @@ export default ({ getService }: FtrProviderContext) => {
);
});
it('generates multiple signals with multiple matches', async () => {
it('generates multiple alerts with multiple matches', async () => {
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_query: '*:*',
@ -1534,7 +1538,7 @@ export default ({ getService }: FtrProviderContext) => {
});
// https://github.com/elastic/kibana/issues/149920
// creates same number of alerts similarly to "generates multiple signals with multiple matches" test
// creates same number of alerts similarly to "generates multiple alerts with multiple matches" test
it('generates alerts with multiple matches if queries contain field path wildcards', async () => {
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
// source.po* matches port source.port field
@ -1605,16 +1609,16 @@ export default ({ getService }: FtrProviderContext) => {
const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 });
expect(previewAlerts.length).equal(88);
const fullSource = previewAlerts.find(
(signal) =>
(signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
(alert) =>
(alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
);
const fullSignal = fullSource?._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = fullSource?._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical');
expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(70);
expect(fullAlert?.host?.risk?.calculated_level).to.eql('Critical');
expect(fullAlert?.host?.risk?.calculated_score_norm).to.eql(70);
});
});
});

View file

@ -21,51 +21,56 @@ import {
ALERT_ORIGINAL_TIME,
ALERT_THRESHOLD_RESULT,
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
import {
createRule,
getOpenSignals,
getOpenAlerts,
getPreviewAlerts,
getThresholdRuleForSignalTesting,
getThresholdRuleForAlertTesting,
previewRule,
} from '../../utils';
import { FtrProviderContext } from '../../common/ftr_provider_context';
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
const log = getService('log');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');
describe('Threshold type rules', () => {
describe('@ess @serverless Threshold type rules', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load(path);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(path);
});
// First test creates a real rule - remaining tests use preview API
it('generates 1 signal from Threshold rules when threshold is met', async () => {
it('generates 1 alert from Threshold rules when threshold is met', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: ['host.id'],
value: 700,
},
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).eql(1);
const fullSignal = alerts.hits.hits[0]._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = alerts.hits.hits[0]._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id);
expect(fullSignal).eql({
...fullSignal,
const eventIds = (fullAlert?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id);
expect(fullAlert).eql({
...fullAlert,
'host.id': '8cc95778cce5407c809480e8e32ad76b',
[EVENT_KIND]: 'signal',
[ALERT_ANCESTORS]: [
@ -77,9 +82,9 @@ export default ({ getService }: FtrProviderContext) => {
},
],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_REASON]: 'event created high alert Signal Testing Query.',
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
[ALERT_REASON]: 'event created high alert Alert Testing Query.',
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_DEPTH]: 1,
[ALERT_THRESHOLD_RESULT]: {
terms: [
@ -94,9 +99,9 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('generates max signals warning when circuit breaker is exceeded', async () => {
it('generates max alerts warning when circuit breaker is exceeded', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 1, // This value generates 7 alerts with the current esArchive
@ -104,12 +109,12 @@ export default ({ getService }: FtrProviderContext) => {
max_signals: 5,
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).contain(getMaxSignalsWarning());
expect(logs[0].warnings).contain(getMaxAlertsWarning());
});
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
it("doesn't generate max alerts warning when circuit breaker is met but not exceeded", async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 1, // This value generates 7 alerts with the current esArchive
@ -117,12 +122,12 @@ export default ({ getService }: FtrProviderContext) => {
max_signals: 7,
};
const { logs } = await previewRule({ supertest, rule });
expect(logs[0].warnings).not.contain(getMaxSignalsWarning());
expect(logs[0].warnings).not.contain(getMaxAlertsWarning());
});
it('generates 2 signals from Threshold rules when threshold is met', async () => {
it('generates 2 alerts from Threshold rules when threshold is met', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 100,
@ -135,7 +140,7 @@ export default ({ getService }: FtrProviderContext) => {
it('applies the provided query before bucketing ', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
query: 'host.id:"2ab45fc1c41e4c84bbd02202a7e5761f"',
threshold: {
field: 'process.name',
@ -147,9 +152,9 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).eql(1);
});
it('generates no signals from Threshold rules when threshold is met and cardinality is not met', async () => {
it('generates no alerts from Threshold rules when threshold is met and cardinality is not met', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 100,
@ -166,9 +171,9 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).eql(0);
});
it('generates no signals from Threshold rules when cardinality is met and threshold is not met', async () => {
it('generates no alerts from Threshold rules when cardinality is met and threshold is not met', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 1000,
@ -185,9 +190,9 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).eql(0);
});
it('generates signals from Threshold rules when threshold and cardinality are both met', async () => {
it('generates alerts from Threshold rules when threshold and cardinality are both met', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.id',
value: 100,
@ -202,13 +207,13 @@ export default ({ getService }: FtrProviderContext) => {
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts.length).eql(1);
const fullSignal = previewAlerts[0]._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = previewAlerts[0]._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id);
expect(fullSignal).eql({
...fullSignal,
const eventIds = (fullAlert?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id);
expect(fullAlert).eql({
...fullAlert,
'host.id': '8cc95778cce5407c809480e8e32ad76b',
[EVENT_KIND]: 'signal',
[ALERT_ANCESTORS]: [
@ -220,9 +225,9 @@ export default ({ getService }: FtrProviderContext) => {
},
],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_REASON]: `event created high alert Signal Testing Query.`,
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
[ALERT_REASON]: `event created high alert Alert Testing Query.`,
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_DEPTH]: 1,
[ALERT_THRESHOLD_RESULT]: {
terms: [
@ -243,9 +248,9 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('should not generate signals if only one field meets the threshold requirement', async () => {
it('should not generate alerts if only one field meets the threshold requirement', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: ['host.id', 'process.name'],
value: 22,
@ -256,9 +261,9 @@ export default ({ getService }: FtrProviderContext) => {
expect(previewAlerts.length).eql(0);
});
it('generates signals from Threshold rules when bucketing by multiple fields', async () => {
it('generates alerts from Threshold rules when bucketing by multiple fields', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: ['host.id', 'process.name', 'event.module'],
value: 21,
@ -267,13 +272,13 @@ export default ({ getService }: FtrProviderContext) => {
const { previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts.length).eql(1);
const fullSignal = previewAlerts[0]._source;
if (!fullSignal) {
return expect(fullSignal).to.be.ok();
const fullAlert = previewAlerts[0]._source;
if (!fullAlert) {
return expect(fullAlert).to.be.ok();
}
const eventIds = (fullSignal[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id);
expect(fullSignal).eql({
...fullSignal,
const eventIds = (fullAlert[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id);
expect(fullAlert).eql({
...fullAlert,
'event.module': 'system',
'host.id': '2ab45fc1c41e4c84bbd02202a7e5761f',
'process.name': 'sshd',
@ -287,9 +292,9 @@ export default ({ getService }: FtrProviderContext) => {
},
],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_REASON]: `event with process sshd, created high alert Signal Testing Query.`,
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
[ALERT_REASON]: `event with process sshd, created high alert Alert Testing Query.`,
[ALERT_RULE_UUID]: fullAlert[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullAlert[ALERT_ORIGINAL_TIME],
[ALERT_DEPTH]: 1,
[ALERT_THRESHOLD_RESULT]: {
terms: [
@ -315,15 +320,15 @@ export default ({ getService }: FtrProviderContext) => {
// https://github.com/elastic/kibana/issues/149920
it('generates 1 alert when threshold is met and rule query has wildcard in field name', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
query: 'agent.ty*:auditbeat', // this query should match all documents from index and we will receive 1 alert, similarly to "generates 1 signal from Threshold rules when threshold is met" test case
...getThresholdRuleForAlertTesting(['auditbeat-*']),
query: 'agent.ty*:auditbeat', // this query should match all documents from index and we will receive 1 alert, similarly to "generates 1 alert from Threshold rules when threshold is met" test case
threshold: {
field: ['host.id'],
value: 700,
},
};
const createdRule = await createRule(supertest, log, rule);
const alerts = await getOpenSignals(supertest, log, es, createdRule);
const alerts = await getOpenAlerts(supertest, log, es, createdRule);
expect(alerts.hits.hits.length).eql(1);
});
@ -342,7 +347,7 @@ export default ({ getService }: FtrProviderContext) => {
it('applies timestamp override when using single field', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['timestamp-fallback-test']),
...getThresholdRuleForAlertTesting(['timestamp-fallback-test']),
threshold: {
field: 'host.name',
value: 1,
@ -370,7 +375,7 @@ export default ({ getService }: FtrProviderContext) => {
it('applies timestamp override when using multiple fields', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['timestamp-fallback-test']),
...getThresholdRuleForAlertTesting(['timestamp-fallback-test']),
threshold: {
field: ['host.name', 'source.ip'],
value: 1,
@ -408,7 +413,7 @@ export default ({ getService }: FtrProviderContext) => {
it('should be enriched with host risk score', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['auditbeat-*']),
...getThresholdRuleForAlertTesting(['auditbeat-*']),
threshold: {
field: 'host.name',
value: 100,

View file

@ -7,16 +7,15 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import {
createRule,
createSignalsIndex,
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
getEqlRuleForSignalTesting,
getSignalsById,
getEqlRuleForAlertTesting,
getAlertsById,
waitForRuleSuccess,
waitForSignalsToBePresent,
waitForAlertsToBePresent,
} from '../../utils';
interface Ignore {
@ -26,10 +25,10 @@ interface Ignore {
testing_regex?: string;
}
// eslint-disable-next-line import/no-default-export
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default ({ getService }: FtrProviderContext): void => {
/**
* See the config file (detection_engine_api_integration/common/config.ts) for which field values were added to be ignored
* See the config file (config.ts) for which field values were added to be ignored
* for testing. The values should be in the config around the area of:
* --xpack.securitySolution.alertIgnoreFields=[testing.ignore_1,/[testingRegex]
* meaning that the ignore fields values should be the array: ["testing.ignore_1", "/[testingRegex]/"]
@ -47,7 +46,7 @@ export default ({ getService }: FtrProviderContext): void => {
* server/lib/detection_engine/signals/source_fields_merging/utils/is_ignored.ts
* server/lib/detection_engine/signals/source_fields_merging/utils/is_eql_bug_77152.ts
*/
describe('ignore_fields', () => {
describe('@ess @serverless ignore_fields', () => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const log = getService('log');
@ -62,7 +61,7 @@ export default ({ getService }: FtrProviderContext): void => {
});
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
@ -70,14 +69,14 @@ export default ({ getService }: FtrProviderContext): void => {
await deleteAllRules(supertest, log);
});
it('should ignore the field of "testing_ignored"', async () => {
const rule = getEqlRuleForSignalTesting(['ignore_fields']);
it('@skipInQA should ignore the field of "testing_ignored"', async () => {
const rule = getEqlRuleForAlertTesting(['ignore_fields']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((hit) => (hit._source as Ignore).testing_ignored)
.sort();
@ -85,27 +84,27 @@ export default ({ getService }: FtrProviderContext): void => {
expect(hits).to.eql([undefined, undefined, undefined, undefined]);
});
it('should ignore the field of "testing_regex"', async () => {
const rule = getEqlRuleForSignalTesting(['ignore_fields']);
it('@skipInQA should ignore the field of "testing_regex"', async () => {
const rule = getEqlRuleForAlertTesting(['ignore_fields']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => (hit._source as Ignore).testing_regex).sort();
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => (hit._source as Ignore).testing_regex).sort();
// Value should be "undefined for all records"
expect(hits).to.eql([undefined, undefined, undefined, undefined]);
});
it('should have the field of "normal_constant"', async () => {
const rule = getEqlRuleForSignalTesting(['ignore_fields']);
const rule = getEqlRuleForAlertTesting(['ignore_fields']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((hit) => (hit._source as Ignore).normal_constant)
.sort();
@ -115,13 +114,13 @@ export default ({ getService }: FtrProviderContext): void => {
// TODO: Remove this test once https://github.com/elastic/elasticsearch/issues/77152 is fixed
it('should ignore the field of "_ignored" when using EQL and index the data', async () => {
const rule = getEqlRuleForSignalTesting(['ignore_fields']);
const rule = getEqlRuleForAlertTesting(['ignore_fields']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => (hit._source as Ignore).small_field).sort();
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => (hit._source as Ignore).small_field).sort();
// We just test a constant value to ensure this did not blow up on us and did index data.
expect(hits).to.eql([

View file

@ -0,0 +1,18 @@
/*
* 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 { FtrProviderContext } from '../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rule execution logic API', function () {
loadTestFile(require.resolve('./keyword_family'));
loadTestFile(require.resolve('./ignore_fields'));
loadTestFile(require.resolve('./runtime'));
loadTestFile(require.resolve('./execution_logic'));
loadTestFile(require.resolve('./timestamps'));
});
}

View file

@ -12,28 +12,27 @@ import {
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import {
createRule,
createSignalsIndex,
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
getEqlRuleForSignalTesting,
getRuleForSignalTesting,
getSignalsById,
getThresholdRuleForSignalTesting,
getEqlRuleForAlertTesting,
getRuleForAlertTesting,
getAlertsById,
getThresholdRuleForAlertTesting,
waitForRuleSuccess,
waitForSignalsToBePresent,
waitForAlertsToBePresent,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const log = getService('log');
const es = getService('es');
describe('Rule detects against a keyword of event.dataset', () => {
describe('@ess @serverless Rule detects against a keyword of event.dataset', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_keyword_family/const_keyword');
});
@ -45,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => {
});
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
@ -54,28 +53,28 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('"kql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 4 signals', async () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 4 alerts', async () => {
const rule = {
...getRuleForSignalTesting(['const_keyword']),
...getRuleForAlertTesting(['const_keyword']),
query: 'event.dataset: "dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
expect(signalsOpen.hits.hits.length).to.eql(4);
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
expect(alertsOpen.hits.hits.length).to.eql(4);
});
it('should copy the dataset_name_1 from the index into the signal', async () => {
it('should copy the dataset_name_1 from the index into the alert', async () => {
const rule = {
...getRuleForSignalTesting(['const_keyword']),
...getRuleForAlertTesting(['const_keyword']),
query: 'event.dataset: "dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
expect(hits).to.eql([
'dataset_name_1',
'dataset_name_1',
@ -86,30 +85,30 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('"eql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 4 signals', async () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 4 alerts', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['const_keyword']),
...getEqlRuleForAlertTesting(['const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
expect(signalsOpen.hits.hits.length).to.eql(4);
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
expect(alertsOpen.hits.hits.length).to.eql(4);
});
it('should copy the "dataset_name_1" from "event.dataset"', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['const_keyword']),
...getEqlRuleForAlertTesting(['const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
expect(hits).to.eql([
'dataset_name_1',
'dataset_name_1',
@ -122,7 +121,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"threshold" rule type', async () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['const_keyword']),
...getThresholdRuleForAlertTesting(['const_keyword']),
threshold: {
field: 'event.dataset',
value: 1,
@ -130,9 +129,9 @@ export default ({ getService }: FtrProviderContext) => {
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_THRESHOLD_RESULT] ?? null)
.sort();
expect(hits).to.eql([

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile }: FtrProviderContext): void => {
describe('Detection keyword family data types', function () {
loadTestFile(require.resolve('./keyword'));

View file

@ -13,21 +13,20 @@ import {
ThresholdRuleCreateProps,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import {
createRule,
createSignalsIndex,
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
getEqlRuleForSignalTesting,
getRuleForSignalTesting,
getSignalsById,
getThresholdRuleForSignalTesting,
getEqlRuleForAlertTesting,
getRuleForAlertTesting,
getAlertsById,
getThresholdRuleForAlertTesting,
waitForRuleSuccess,
waitForSignalsToBePresent,
waitForAlertsToBePresent,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
@ -44,7 +43,7 @@ export default ({ getService }: FtrProviderContext) => {
});
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
@ -52,17 +51,17 @@ export default ({ getService }: FtrProviderContext) => {
await deleteAllRules(supertest, log);
});
describe('"kql" rule type', () => {
describe('@ess @serverless "kql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['keyword']),
...getRuleForAlertTesting(['keyword']),
query: 'event.dataset: "dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
expect(hits).to.eql([
'dataset_name_1',
'dataset_name_1',
@ -75,15 +74,15 @@ export default ({ getService }: FtrProviderContext) => {
describe('"eql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['keyword']),
...getEqlRuleForAlertTesting(['keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
expect(hits).to.eql([
'dataset_name_1',
'dataset_name_1',
@ -96,7 +95,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"threshold" rule type', async () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: ThresholdRuleCreateProps = {
...getThresholdRuleForSignalTesting(['keyword']),
...getThresholdRuleForAlertTesting(['keyword']),
threshold: {
field: 'event.dataset',
value: 1,
@ -104,9 +103,9 @@ export default ({ getService }: FtrProviderContext) => {
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_THRESHOLD_RESULT] ?? null)
.sort();
expect(hits).to.eql([

View file

@ -12,27 +12,26 @@ import {
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import {
createRule,
createSignalsIndex,
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
getEqlRuleForSignalTesting,
getRuleForSignalTesting,
getSignalsById,
getEqlRuleForAlertTesting,
getRuleForAlertTesting,
getAlertsById,
waitForRuleSuccess,
waitForSignalsToBePresent,
waitForAlertsToBePresent,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const log = getService('log');
const es = getService('es');
describe('Rule detects against a keyword and constant_keyword of event.dataset', () => {
describe('@ess @serverless Rule detects against a keyword and constant_keyword of event.dataset', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_keyword_family/const_keyword');
await esArchiver.load('x-pack/test/functional/es_archives/rule_keyword_family/keyword');
@ -46,7 +45,7 @@ export default ({ getService }: FtrProviderContext) => {
});
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
@ -55,28 +54,28 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('"kql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 8 signals, 4 from each index', async () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 8 alerts, 4 from each index', async () => {
const rule = {
...getRuleForSignalTesting(['keyword', 'const_keyword']),
...getRuleForAlertTesting(['keyword', 'const_keyword']),
query: 'event.dataset: "dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 8, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
expect(signalsOpen.hits.hits.length).to.eql(8);
await waitForAlertsToBePresent(supertest, log, 8, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
expect(alertsOpen.hits.hits.length).to.eql(8);
});
it('should copy the dataset_name_1 from the index into the signal', async () => {
it('should copy the dataset_name_1 from the index into the alert', async () => {
const rule = {
...getRuleForSignalTesting(['keyword', 'const_keyword']),
...getRuleForAlertTesting(['keyword', 'const_keyword']),
query: 'event.dataset: "dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 8, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
await waitForAlertsToBePresent(supertest, log, 8, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
expect(hits).to.eql([
'dataset_name_1',
'dataset_name_1',
@ -91,30 +90,30 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('"eql" rule type', () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 8 signals, 4 from each index', async () => {
it('should detect the "dataset_name_1" from "event.dataset" and have 8 alerts, 4 from each index', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['keyword', 'const_keyword']),
...getEqlRuleForAlertTesting(['keyword', 'const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 8, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
expect(signalsOpen.hits.hits.length).to.eql(8);
await waitForAlertsToBePresent(supertest, log, 8, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
expect(alertsOpen.hits.hits.length).to.eql(8);
});
it('should copy the "dataset_name_1" from "event.dataset"', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['keyword', 'const_keyword']),
...getEqlRuleForAlertTesting(['keyword', 'const_keyword']),
query: 'any where event.dataset=="dataset_name_1"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 8, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
await waitForAlertsToBePresent(supertest, log, 8, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
expect(hits).to.eql([
'dataset_name_1',
'dataset_name_1',
@ -131,7 +130,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('"threshold" rule type', async () => {
it('should detect the "dataset_name_1" from "event.dataset"', async () => {
const rule: ThresholdRuleCreateProps = {
...getRuleForSignalTesting(['keyword', 'const_keyword']),
...getRuleForAlertTesting(['keyword', 'const_keyword']),
rule_id: 'threshold-rule',
type: 'threshold',
language: 'kuery',
@ -143,9 +142,9 @@ export default ({ getService }: FtrProviderContext) => {
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_THRESHOLD_RESULT] ?? null)
.sort();
expect(hits).to.eql([

View file

@ -8,19 +8,18 @@
import expect from '@kbn/expect';
import { performance } from 'perf_hooks';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
createRule,
createSignalsIndex,
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
getRuleForSignalTesting,
getSignalsById,
getRuleForAlertTesting,
getAlertsById,
waitForRuleSuccess,
waitForSignalsToBePresent,
waitForAlertsToBePresent,
} from '../../utils';
import { FtrProviderContext } from '../../../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
@ -33,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => {
}
// FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/138923
describe('Tests involving runtime fields of source indexes and the signals index', () => {
describe('@ess @serverless Tests involving runtime fields of source indexes and the alerts index', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/runtime');
});
@ -44,7 +43,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('Regular runtime field mappings', () => {
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
@ -53,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => {
});
it('should execute a rule to completion and not timeout when there are a lot of runtime fields', async () => {
const rule = getRuleForSignalTesting(['runtime']);
const rule = getRuleForAlertTesting(['runtime']);
const { id } = await createRule(supertest, log, rule);
const start = performance.now();
await waitForRuleSuccess({ supertest, log, id });
@ -61,26 +60,26 @@ export default ({ getService }: FtrProviderContext) => {
expect(end - start).to.be.lessThan(10000);
});
it('should copy normal non-runtime data set from the source index into the signals index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForSignalTesting(['runtime']);
it('should copy normal non-runtime data set from the source index into the alerts index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForAlertTesting(['runtime']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
.map((signal) => (signal._source?.host as Runtime).name)
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((alert) => (alert._source?.host as Runtime).name)
.sort();
expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']);
});
it('should copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForSignalTesting(['runtime']);
it('should copy "runtime mapping" data from a source index into the alerts index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForAlertTesting(['runtime']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
.map((signal) => (signal._source?.host as Runtime).hostname)
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((alert) => (alert._source?.host as Runtime).hostname)
.sort();
expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']);
});
@ -88,7 +87,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('Runtime field mappings that have conflicts within them', () => {
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
await esArchiver.load(
'x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields'
);
@ -107,14 +106,14 @@ export default ({ getService }: FtrProviderContext) => {
* risk with overwriting fields in the strategy we are currently using in detection engine. If you swap, change the strategies
* because we decide to overwrite "_source" values with "fields", then expect to change this test.
*/
it('should NOT copy normal non-runtime data set from the source index into the signals index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForSignalTesting(['runtime_conflicting_fields']);
it('should NOT copy normal non-runtime data set from the source index into the alerts index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForAlertTesting(['runtime_conflicting_fields']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits
.map((signal) => signal._source?.host as Array<{ name: string }>)
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits
.map((alert) => alert._source?.host as Array<{ name: string }>)
.map((host) => {
// sort the inner array elements first
return host.sort((a, b) => a.name.localeCompare(b.name));
@ -164,15 +163,13 @@ export default ({ getService }: FtrProviderContext) => {
* fields as arrays of objects since the objects are flattened in "fields" and we detect something already there so we skip
* this shadowed runtime data as it is ambiguous of where we would put it in the array.
*/
it('should NOT copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForSignalTesting(['runtime_conflicting_fields']);
it('should NOT copy "runtime mapping" data from a source index into the alerts index in the same position when the target is ECS compatible', async () => {
const rule = getRuleForAlertTesting(['runtime_conflicting_fields']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 4, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map(
(signal) => (signal._source?.host as Runtime).hostname
);
await waitForAlertsToBePresent(supertest, log, 4, [id]);
const alertsOpen = await getAlertsById(supertest, log, id);
const hits = alertsOpen.hits.hits.map((alert) => (alert._source?.host as Runtime).hostname);
expect(hits).to.eql([undefined, undefined, undefined, undefined]);
});
});

View file

@ -14,37 +14,41 @@ import {
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
createSignalsIndex,
createAlertsIndex,
deleteAllRules,
deleteAllAlerts,
createRule,
waitForRuleSuccess,
waitForSignalsToBePresent,
getOpenSignals,
getRuleForSignalTesting,
getSignalsByIds,
getEqlRuleForSignalTesting,
waitForAlertsToBePresent,
getOpenAlerts,
getRuleForAlertTesting,
getAlertsByIds,
getEqlRuleForAlertTesting,
waitForRulePartialFailure,
} from '../../utils';
import { FtrProviderContext } from '../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../es_archive_path_builder';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const es = getService('es');
const log = getService('log');
// TODO: add a new service
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');
/**
* Tests around timestamps within signals such as the copying of timestamps correctly into
* Tests around timestamps within alerts such as the copying of timestamps correctly into
* the "signal.original_time" field, ensuring that timestamp overrides operate, and ensuring that
* partial errors happen correctly
*/
describe('timestamp tests', () => {
describe('Signals generated from events with a timestamp in seconds is converted correctly into the forced ISO8601 format when copying', () => {
describe('@ess @serverless timestamp tests', () => {
describe('alerts generated from events with a timestamp in seconds is converted correctly into the forced ISO8601 format when copying', () => {
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
await esArchiver.load(
'x-pack/test/functional/es_archives/security_solution/timestamp_in_seconds'
);
@ -66,58 +70,50 @@ export default ({ getService }: FtrProviderContext) => {
describe('KQL query', () => {
it('should convert the @timestamp which is epoch_seconds into the correct ISO format', async () => {
const rule = getRuleForSignalTesting(['timestamp_in_seconds']);
const rule = getRuleForAlertTesting(['timestamp_in_seconds']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsByIds(supertest, log, [id]);
const hits = signalsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME])
.sort();
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]).sort();
expect(hits).to.eql(['2021-06-02T23:33:15.000Z']);
});
it('should still use the @timestamp field even with an override field. It should never use the override field', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['myfakeindex-5']),
...getRuleForAlertTesting(['myfakeindex-5']),
timestamp_override: 'event.ingested',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsByIds(supertest, log, [id]);
const hits = signalsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME])
.sort();
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]).sort();
expect(hits).to.eql(['2020-12-16T15:16:18.000Z']);
});
});
describe('EQL query', () => {
it('should convert the @timestamp which is epoch_seconds into the correct ISO format for EQL', async () => {
const rule = getEqlRuleForSignalTesting(['timestamp_in_seconds']);
const rule = getEqlRuleForAlertTesting(['timestamp_in_seconds']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsByIds(supertest, log, [id]);
const hits = signalsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME])
.sort();
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]).sort();
expect(hits).to.eql(['2021-06-02T23:33:15.000Z']);
});
it('should still use the @timestamp field even with an override field. It should never use the override field', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['myfakeindex-5']),
...getEqlRuleForAlertTesting(['myfakeindex-5']),
timestamp_override: 'event.ingested',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsByIds(supertest, log, [id]);
const hits = signalsOpen.hits.hits
.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME])
.sort();
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const hits = alertsOpen.hits.hits.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME]).sort();
expect(hits).to.eql(['2020-12-16T15:16:18.000Z']);
});
});
@ -129,10 +125,10 @@ export default ({ getService }: FtrProviderContext) => {
* If no timestamp override field exists in the indices but one was provided to the rule,
* the rule's query will additionally search for events using the `@timestamp` field
*/
describe('Signals generated from events with timestamp override field', async () => {
describe('alerts generated from events with timestamp override field', async () => {
beforeEach(async () => {
await deleteAllAlerts(supertest, log, es);
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
await esArchiver.load(
'x-pack/test/functional/es_archives/security_solution/timestamp_override_1'
);
@ -165,9 +161,9 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('KQL', () => {
it('should generate signals with event.ingested, @timestamp and (event.ingested + timestamp)', async () => {
it('should generate alerts with event.ingested, @timestamp and (event.ingested + timestamp)', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['myfa*']),
...getRuleForAlertTesting(['myfa*']),
timestamp_override: 'event.ingested',
};
@ -178,17 +174,17 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 3, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id], 3);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
await waitForAlertsToBePresent(supertest, log, 3, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id], 3);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
const alertsOrderedByEventId = orderBy(alerts, 'alert.parent.id', 'asc');
expect(signalsOrderedByEventId.length).equal(3);
expect(alertsOrderedByEventId.length).equal(3);
});
it('should generate 2 signals with event.ingested when timestamp fallback is disabled', async () => {
it('should generate 2 alerts with event.ingested when timestamp fallback is disabled', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['myfa*']),
...getRuleForAlertTesting(['myfa*']),
rule_id: 'rule-without-timestamp-fallback',
timestamp_override: 'event.ingested',
timestamp_override_fallback_disabled: true,
@ -201,16 +197,16 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 2, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id], 2);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
await waitForAlertsToBePresent(supertest, log, 2, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id], 2);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
const alertsOrderedByEventId = orderBy(alerts, 'alert.parent.id', 'asc');
expect(signalsOrderedByEventId.length).equal(2);
expect(alertsOrderedByEventId.length).equal(2);
});
it('should generate 2 signals with @timestamp', async () => {
const rule: QueryRuleCreateProps = getRuleForSignalTesting(['myfa*']);
it('should generate 2 alerts with @timestamp', async () => {
const rule: QueryRuleCreateProps = getRuleForAlertTesting(['myfa*']);
const { id } = await createRule(supertest, log, rule);
@ -219,17 +215,17 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 2, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id]);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
await waitForAlertsToBePresent(supertest, log, 2, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id]);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
const alertsOrderedByEventId = orderBy(alerts, 'alert.parent.id', 'asc');
expect(signalsOrderedByEventId.length).equal(2);
expect(alertsOrderedByEventId.length).equal(2);
});
it('should generate 2 signals when timestamp override does not exist', async () => {
it('should generate 2 alerts when timestamp override does not exist', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['myfa*']),
...getRuleForAlertTesting(['myfa*']),
timestamp_override: 'event.fakeingestfield',
};
const { id } = await createRule(supertest, log, rule);
@ -239,50 +235,50 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 2, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id, id]);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
await waitForAlertsToBePresent(supertest, log, 2, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id, id]);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
const alertsOrderedByEventId = orderBy(alerts, 'alert.parent.id', 'asc');
expect(signalsOrderedByEventId.length).equal(2);
expect(alertsOrderedByEventId.length).equal(2);
});
it('should not generate any signals when timestamp override does not exist and timestamp fallback is disabled', async () => {
it('should not generate any alerts when timestamp override does not exist and timestamp fallback is disabled', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['myfa*']),
...getRuleForAlertTesting(['myfa*']),
rule_id: 'rule-without-timestamp-fallback',
timestamp_override: 'event.fakeingestfield',
timestamp_override_fallback_disabled: true,
};
const createdRule = await createRule(supertest, log, rule);
const signalsOpen = await getOpenSignals(
const alertsOpen = await getOpenAlerts(
supertest,
log,
es,
createdRule,
RuleExecutionStatusEnum['partial failure']
);
expect(signalsOpen.hits.hits.length).eql(0);
expect(alertsOpen.hits.hits.length).eql(0);
});
/**
* We should not use the timestamp override as the "original_time" as that can cause
* confusion if you have both a timestamp and an override in the source event. Instead the "original_time"
* field should only be overridden by the "timestamp" since when we generate a signal
* and we add a new timestamp to the signal.
* field should only be overridden by the "timestamp" since when we generate a alert
* and we add a new timestamp to the alert.
*/
it('should NOT use the timestamp override as the "original_time"', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['myfakeindex-2']),
...getRuleForAlertTesting(['myfakeindex-2']),
timestamp_override: 'event.ingested',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id, id]);
const hits = signalsResponse.hits.hits
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id, id]);
const hits = alertsResponse.hits.hits
.map((hit) => hit._source?.[ALERT_ORIGINAL_TIME])
.sort();
expect(hits).to.eql([undefined]);
@ -290,8 +286,8 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('EQL', () => {
it('should generate 2 signals with @timestamp', async () => {
const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting(['myfa*']);
it('should generate 2 alerts with @timestamp', async () => {
const rule: EqlRuleCreateProps = getEqlRuleForAlertTesting(['myfa*']);
const { id } = await createRule(supertest, log, rule);
@ -300,17 +296,17 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 2, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id]);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
await waitForAlertsToBePresent(supertest, log, 2, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id]);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
const alertsOrderedByEventId = orderBy(alerts, 'alert.parent.id', 'asc');
expect(signalsOrderedByEventId.length).equal(2);
expect(alertsOrderedByEventId.length).equal(2);
});
it('should generate 2 signals when timestamp override does not exist', async () => {
it('should generate 2 alerts when timestamp override does not exist', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['myfa*']),
...getEqlRuleForAlertTesting(['myfa*']),
timestamp_override: 'event.fakeingestfield',
};
const { id } = await createRule(supertest, log, rule);
@ -320,44 +316,44 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 2, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id, id]);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
await waitForAlertsToBePresent(supertest, log, 2, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id, id]);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
const alertsOrderedByEventId = orderBy(alerts, 'alert.parent.id', 'asc');
expect(signalsOrderedByEventId.length).equal(2);
expect(alertsOrderedByEventId.length).equal(2);
});
it('should not generate any signals when timestamp override does not exist and timestamp fallback is disabled', async () => {
it('should not generate any alerts when timestamp override does not exist and timestamp fallback is disabled', async () => {
const rule: EqlRuleCreateProps = {
...getEqlRuleForSignalTesting(['myfa*']),
...getEqlRuleForAlertTesting(['myfa*']),
timestamp_override: 'event.fakeingestfield',
timestamp_override_fallback_disabled: true,
};
const createdRule = await createRule(supertest, log, rule);
const signalsOpen = await getOpenSignals(
const alertsOpen = await getOpenAlerts(
supertest,
log,
es,
createdRule,
RuleExecutionStatusEnum['partial failure']
);
expect(signalsOpen.hits.hits.length).eql(0);
expect(alertsOpen.hits.hits.length).eql(0);
});
});
});
describe('Signals generated from events with timestamp override field and ensures search_after continues to work when documents are missing timestamp override field', () => {
describe('alerts generated from events with timestamp override field and ensures search_after continues to work when documents are missing timestamp override field', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.load(path);
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
await esArchiver.unload(path);
});
beforeEach(async () => {
await createSignalsIndex(supertest, log);
await createAlertsIndex(supertest, log);
});
afterEach(async () => {
@ -377,9 +373,9 @@ export default ({ getService }: FtrProviderContext) => {
*
* ref: https://github.com/elastic/elasticsearch/issues/28806#issuecomment-369303620
*/
it('should generate 200 signals when timestamp override does not exist', async () => {
it('should generate 200 alerts when timestamp override does not exist', async () => {
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['auditbeat-*']),
...getRuleForAlertTesting(['auditbeat-*']),
timestamp_override: 'event.fakeingested',
max_signals: 200,
};
@ -390,11 +386,11 @@ export default ({ getService }: FtrProviderContext) => {
log,
id,
});
await waitForSignalsToBePresent(supertest, log, 200, [id]);
const signalsResponse = await getSignalsByIds(supertest, log, [id], 200);
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
await waitForAlertsToBePresent(supertest, log, 200, [id]);
const alertsResponse = await getAlertsByIds(supertest, log, [id], 200);
const alerts = alertsResponse.hits.hits.map((hit) => hit._source);
expect(signals.length).equal(200);
expect(alerts.length).equal(200);
});
});
});

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { Client } from '@elastic/elasticsearch';
import { ALERT_RULE_UUID } from '@kbn/rule-data-utils';
import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/enrichments/types';
import { refreshIndex } from '../refresh_index';
/**
* Refresh an index, making changes available to search.
* Useful for tests where we want to ensure that a rule does NOT create alerts, e.g. testing exceptions.
* @param es The ElasticSearch handle
*/
export const getPreviewAlerts = async ({
es,
previewId,
size,
sort,
}: {
es: Client;
previewId: string;
size?: number;
sort?: string[];
}) => {
const index = '.preview.alerts-security.alerts-*';
await refreshIndex(es, index);
const query = {
bool: {
filter: {
term: {
[ALERT_RULE_UUID]: previewId,
},
},
},
};
const result = await es.search<DetectionAlert & RiskEnrichmentFields>({
index,
size,
query,
sort,
});
return result.hits.hits;
};

View file

@ -14,9 +14,10 @@ export * from './get_open_alerts';
export * from './get_alerts_by_ids';
export * from './get_query_alerts_ids';
export * from './get_alerts_by_id';
export * from './remove_random_valued_properties';
export * from './remove_random_valued_properties_from_alert';
export * from './set_alert_status';
export * from './get_alert_status_empty_response';
export * from './get_query_alert_ids';
export * from './set_alert_tags';
export * from './get_preview_alerts';
export * from './migrations';

View file

@ -8,7 +8,7 @@
import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { ALERT_LAST_DETECTED, ALERT_START } from '@kbn/rule-data-utils';
export const removeRandomValuedProperties = (alert: DetectionAlert | undefined) => {
export const removeRandomValuedPropertiesFromAlert = (alert: DetectionAlert | undefined) => {
if (!alert) {
return undefined;
}
@ -20,6 +20,7 @@ export const removeRandomValuedProperties = (alert: DetectionAlert | undefined)
'kibana.alert.rule.created_at': createdAt,
'kibana.alert.rule.updated_at': updatedAt,
'kibana.alert.uuid': alertUuid,
'kibana.alert.url': alertURL,
[ALERT_START]: alertStart,
[ALERT_LAST_DETECTED]: lastDetected,
...restOfAlert

View file

@ -9,6 +9,7 @@ export * from './exception_list_and_item';
export * from './alerts';
export * from './actions';
export * from './data_generator';
export * from './machine_learning';
export * from './rules/get_rule_so_by_id';
export * from './rules/create_rule_saved_object';

View file

@ -0,0 +1,7 @@
/*
* 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 './machine_learning_setup';

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type SuperTest from 'supertest';
import { getCommonRequestHeader } from '../../../../../functional/services/ml/common_api';
export const executeSetupModuleRequest = async ({
module,
rspCode,
supertest,
}: {
module: string;
rspCode: number;
supertest: SuperTest.SuperTest<SuperTest.Test>;
}) => {
const { body } = await supertest
.post(`/internal/ml/modules/setup/${module}`)
.set(getCommonRequestHeader('1'))
.send({
prefix: '',
groups: ['auditbeat'],
indexPatternName: 'auditbeat-*',
startDatafeed: false,
useDedicatedIndex: true,
applyToAllSpaces: true,
})
.expect(rspCode);
return body;
};
export const forceStartDatafeeds = async ({
jobId,
rspCode,
supertest,
}: {
jobId: string;
rspCode: number;
supertest: SuperTest.SuperTest<SuperTest.Test>;
}) => {
const { body } = await supertest
.post(`/internal/ml/jobs/force_start_datafeeds`)
.set(getCommonRequestHeader('1'))
.send({
datafeedIds: [`datafeed-${jobId}`],
start: new Date().getUTCMilliseconds(),
})
.expect(rspCode);
return body;
};

View file

@ -19,7 +19,7 @@ export const getRuleForAlertTesting = (
ruleId = 'rule-1',
enabled = true
): QueryRuleCreateProps => ({
name: 'Signal Testing Query',
name: 'Alert Testing Query',
description: 'Tests a simple query',
enabled,
risk_score: 1,

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine';
export const getRuleForAlertTestingWithTimestampOverride = (
index: string[],
ruleId = 'rule-1',
enabled = true,
timestampOverride = 'event.ingested'
): QueryRuleCreateProps => ({
name: 'Alert 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',
});

View file

@ -31,6 +31,8 @@ export * from './get_saved_query_rule_for_alert_testing';
export * from './get_rule_so_by_id';
export * from './create_rule_saved_object';
export * from './get_rule_with_legacy_investigation_fields';
export * from './preview_rule';
export * from './preview_rule_with_exception_entries';
export * from './patch_rule';
export * from './generate_event';
export * from './prebuilt_rules';

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type SuperTest from 'supertest';
import type {
RuleCreateProps,
PreviewRulesSchema,
RulePreviewLogs,
} from '@kbn/security-solution-plugin/common/api/detection_engine';
import { DETECTION_ENGINE_RULES_PREVIEW } from '@kbn/security-solution-plugin/common/constants';
/**
* Runs the preview for a rule. Any generated alerts will be written to .preview.alerts.
* This is much faster than actually running the rule, and can also quickly simulate multiple
* consecutive rule runs, e.g. for ensuring that rule state is properly handled across runs.
* @param supertest The supertest deps
* @param rule The rule to create
*/
export const previewRule = async ({
supertest,
rule,
invocationCount = 1,
timeframeEnd = new Date(),
}: {
supertest: SuperTest.SuperTest<SuperTest.Test>;
rule: RuleCreateProps;
invocationCount?: number;
timeframeEnd?: Date;
}): Promise<{
previewId: string;
logs: RulePreviewLogs[];
isAborted: boolean;
}> => {
const previewRequest: PreviewRulesSchema = {
...rule,
invocationCount,
timeframeEnd: timeframeEnd.toISOString(),
};
const response = await supertest
.post(DETECTION_ENGINE_RULES_PREVIEW)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send(previewRequest)
.expect(200);
return response.body;
};

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ToolingLog } from '@kbn/tooling-log';
import type SuperTest from 'supertest';
import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types';
import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine';
import {
createContainerWithEntries,
createContainerWithEndpointEntries,
} from '../exception_list_and_item';
import { previewRule } from './preview_rule';
/**
* Convenience testing function where you can pass in just the entries and you will
* get a rule created with the entries added to an exception list and exception list item
* all auto-created at once.
* @param supertest super test agent
* @param rule The rule to create and attach an exception list to
* @param entries The entries to create the rule and exception list from
* @param endpointEntries The endpoint entries to create the rule and exception list from
* @param osTypes The os types to optionally add or not to add to the container
*/
export const previewRuleWithExceptionEntries = async ({
supertest,
log,
rule,
entries,
endpointEntries,
invocationCount,
timeframeEnd,
}: {
supertest: SuperTest.SuperTest<SuperTest.Test>;
log: ToolingLog;
rule: RuleCreateProps;
entries: NonEmptyEntriesArray[];
endpointEntries?: Array<{
entries: NonEmptyEntriesArray;
osTypes: OsTypeArray | undefined;
}>;
invocationCount?: number;
timeframeEnd?: Date;
}) => {
const maybeExceptionList = await createContainerWithEntries(supertest, log, entries);
const maybeEndpointList = await createContainerWithEndpointEntries(
supertest,
log,
endpointEntries ?? []
);
return previewRule({
supertest,
rule: {
...rule,
exceptions_list: [...maybeExceptionList, ...maybeEndpointList],
},
invocationCount,
timeframeEnd,
});
};

View file

@ -30,6 +30,8 @@
"@kbn/core-saved-objects-server",
"@kbn/core",
"@kbn/alerting-plugin",
"@kbn/securitysolution-ecs",
"@kbn/securitysolution-rules",
"@kbn/core-http-common",
"@kbn/securitysolution-ecs",
"@kbn/fleet-plugin",

View file

@ -86,7 +86,6 @@
"@kbn/data-views-plugin",
"@kbn/datemath",
"@kbn/safer-lodash-set",
"@kbn/securitysolution-rules",
"@kbn/es-archiver",
"@kbn/config-schema",
"@kbn/kubernetes-security-plugin",