diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_data.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_data.json
index 276c56db2..e2f5447b8 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_data.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_data.json
@@ -27,19 +27,19 @@
"triggers": [
{
"name": "DNS name alert",
- "sev_levels": ["low"],
- "tags": ["dns.low"],
+ "sev_levels": ["high"],
+ "tags": ["dns.high"],
"actions": [
{
"id": "",
- "name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector",
+ "name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector",
"destination_id": "",
"subject_template": {
- "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector",
+ "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: Cypress DNS Detector",
"lang": "mustache"
},
"message_template": {
- "source": "Triggered alert condition: \nSeverity: 1 (Highest)\nThreat detector: Cypress DNS Detector\nDescription: Detects DNS names.\nDetector data sources:\n\tdns",
+ "source": "Triggered alert condition: \nSeverity: 1 (Highest) \nThreat detector: Cypress DNS Detector\nDescription: Detects DNS names.\nDetector data sources:\n\tdns",
"lang": "mustache"
},
"throttle_enabled": false,
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_mappings_data.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_mappings_data.json
index e4056d577..6f9f869ea 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_mappings_data.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_dns_detector_mappings_data.json
@@ -2,15 +2,15 @@
"properties": {
"dns-answers-type": {
"type": "alias",
- "path": "DnsAnswerType"
+ "path": "dns.answers.type"
},
"dns-question-name": {
"type": "alias",
- "path": "DnsQuestionName"
+ "path": "dns.question.name"
},
"dns-question-registered_domain": {
"type": "alias",
- "path": "DnsQuestionRegisteredDomain"
+ "path": "dns.question.registered_domain"
}
}
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_data.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_data.json
index 07392d280..b68c08406 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_data.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_data.json
@@ -27,7 +27,7 @@
"triggers": [
{
"name": "USB plugged in alert",
- "sev_levels": ["low"],
+ "sev_levels": ["high"],
"tags": ["windows.usb"],
"actions": [
{
@@ -35,11 +35,11 @@
"name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: USB Detector",
"destination_id": "",
"subject_template": {
- "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: USB Detector",
+ "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: USB Detector",
"lang": "mustache"
},
"message_template": {
- "source": "Triggered alert condition: \nSeverity: 1 (Highest)\nThreat detector: USB Detector\nDescription: Detect USB plugged in.\nDetector data sources:\n\twindows",
+ "source": "Triggered alert condition: \nSeverity: 1 (Highest) \nThreat detector: USB Detector\nDescription: Detect USB plugged in.\nDetector data sources:\n\twindows",
"lang": "mustache"
},
"throttle_enabled": false,
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_mappings_data.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_mappings_data.json
index da81361fe..d87eac971 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_mappings_data.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_mappings_data.json
@@ -1,28 +1,12 @@
{
"properties": {
- "event_uid": {
+ "winlog.event_id": {
"type": "alias",
"path": "EventID"
},
- "windows-event_data-CommandLine": {
+ "winlog-provider_name": {
"type": "alias",
- "path": "CommandLine"
- },
- "windows-hostname": {
- "type": "alias",
- "path": "HostName"
- },
- "windows-message": {
- "type": "alias",
- "path": "Message"
- },
- "windows-provider-name": {
- "type": "alias",
- "path": "Provider_Name"
- },
- "windows-servicename": {
- "type": "alias",
- "path": "ServiceName"
+ "path": "winlog.provider_name"
}
}
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_dns_index_data.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_dns_index_data.json
index 35077a0f5..901c7c3e3 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_dns_index_data.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_dns_index_data.json
@@ -1,5 +1,5 @@
{
- "DnsAnswerType": "QWE",
- "DnsQuestionRegisteredDomain": "EC2AMAZ-EPWO7HKA",
- "DnsQuestionName": "QWE"
+ "dns.answers.type": "AnswerType",
+ "dns.question.registered_domain": "EC2AMAZ-EPWO7HKA",
+ "dns.question.name": "QuestionName"
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_windows_index_data.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_windows_index_data.json
index c449c7584..526859053 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_windows_index_data.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/add_windows_index_data.json
@@ -1,39 +1,3 @@
{
- "EventTime": "2020-02-04T14:59:39.343541+00:00",
- "HostName": "EC2AMAZ-EPO7HKA",
- "Keywords": "9223372036854775808",
- "SeverityValue": 2,
- "Severity": "ERROR",
- "EventID": 2003,
- "SourceName": "Microsoft-Windows-Sysmon",
- "ProviderGuid": "{5770385F-C22A-43E0-BF4C-06F5698FFBD9}",
- "Version": 5,
- "TaskValue": 22,
- "OpcodeValue": 0,
- "RecordNumber": 9532,
- "ExecutionProcessID": 1996,
- "ExecutionThreadID": 2616,
- "Channel": "Microsoft-Windows-Sysmon/Operational",
- "Domain": "NT AUTHORITY",
- "AccountName": "SYSTEM",
- "UserID": "S-1-5-18",
- "AccountType": "User",
- "Message": "Dns query:\r\nRuleName: \r\nUtcTime: 2020-02-04 14:59:38.349\r\nProcessGuid: {b3c285a4-3cda-5dc0-0000-001077270b00}\r\nProcessId: 1904\r\nQueryName: EC2AMAZ-EPO7HKA\r\nQueryStatus: 0\r\nQueryResults: 172.31.46.38;\r\nImage: C:\\Program Files\\nxlog\\nxlog.exe",
- "Category": "Dns query (rule: DnsQuery)",
- "Opcode": "Info",
- "UtcTime": "2020-02-04 14:59:38.349",
- "ProcessGuid": "{b3c285a4-3cda-5dc0-0000-001077270b00}",
- "ProcessId": "1904",
- "QueryName": "EC2AMAZ-EPO7HKA",
- "QueryStatus": "0",
- "QueryResults": "172.31.46.38;",
- "Image": "C:\\Program Files\\nxlog\\regsvr32.exe",
- "EventReceivedTime": "2020-02-04T14:59:40.780905+00:00",
- "SourceModuleName": "in",
- "SourceModuleType": "im_msvistalog",
- "CommandLine": "eachtest",
- "Initiated": "true",
- "Provider_Name": "Service_ws_Control_ws_Manager",
- "TargetObject": "\\SOFTWARE\\Microsoft\\Office\\Outlook\\Security",
- "EventType": "SetValue"
+ "EventID": "2003"
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_dns_settings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_dns_settings.json
index 126659dc6..970a6089a 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_dns_settings.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_dns_settings.json
@@ -1,13 +1,13 @@
{
"mappings": {
"properties": {
- "DnsAnswerType": {
+ "dns.answers.type": {
"type": "text"
},
- "DnsQuestionRegisteredDomain": {
+ "dns.question.name": {
"type": "text"
},
- "DnsQuestionName": {
+ "dns.question.registered_domain": {
"type": "text"
}
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_windows_settings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_windows_settings.json
index f794e671e..02c187caf 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_windows_settings.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/index/create_windows_settings.json
@@ -1,22 +1,10 @@
{
"mappings": {
"properties": {
- "CommandLine": {
- "type": "text"
- },
"EventID": {
"type": "integer"
},
- "HostName": {
- "type": "text"
- },
- "Message": {
- "type": "text"
- },
- "Provider_Name": {
- "type": "text"
- },
- "ServiceName": {
+ "winlog.provider_name": {
"type": "text"
}
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_name_selection.json
similarity index 72%
rename from cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule.json
rename to cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_name_selection.json
index 5e38ab4bd..7c1e7c8fb 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_name_selection.json
@@ -12,12 +12,12 @@
],
"tags": [
{
- "value": "dns.low"
+ "value": "dns.high"
}
],
"log_source": "",
- "detection": "selection:\n query:\n - QWE\n - ASD\n - YXC\ncondition: selection",
- "level": "low",
+ "detection": "selection:\n dns-question-name:\n - QuestionName\ncondition: selection",
+ "level": "high",
"false_positives": [
{
"value": ""
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_type_selection.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_type_selection.json
new file mode 100644
index 000000000..e447a30d5
--- /dev/null
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_type_selection.json
@@ -0,0 +1,26 @@
+{
+ "id": "25b9c01c-350d-4b95-bed1-836d04a4f325",
+ "category": "dns",
+ "title": "Cypress DNS Type Rule",
+ "description": "Detects DNS type as QWE",
+ "status": "experimental",
+ "author": "Cypress Tests",
+ "references": [
+ {
+ "value": ""
+ }
+ ],
+ "tags": [
+ {
+ "value": "dns.high"
+ }
+ ],
+ "log_source": "",
+ "detection": "selection:\n dns-answers-type:\n - AnswerType\ncondition: selection",
+ "level": "high",
+ "false_positives": [
+ {
+ "value": ""
+ }
+ ]
+}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_network_rule.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_network_rule.json
index 43e69cff4..2937fc79d 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_network_rule.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_network_rule.json
@@ -12,12 +12,12 @@
],
"tags": [
{
- "value": "network.low"
+ "value": "network.high"
}
],
"log_source": "",
"detection": "selection:\n keywords:\n - erase\n - delete\n - YXC\ncondition: selection",
- "level": "low",
+ "level": "high",
"false_positives": [
{
"value": ""
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_windows_usb_rule.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_windows_usb_rule.json
index 20f59799a..897b9dc0e 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_windows_usb_rule.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_windows_usb_rule.json
@@ -17,7 +17,7 @@
],
"log_source": "",
"detection": "selection:\n EventID:\n - 2003\n - 2100\n - 2102\ncondition: selection",
- "level": "low",
+ "level": "high",
"false_positives": [
{
"value": ""
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/sample_dns_field_mappings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/sample_dns_field_mappings.json
new file mode 100644
index 000000000..b2f9b698e
--- /dev/null
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/sample_dns_field_mappings.json
@@ -0,0 +1,5 @@
+{
+ "dns-question-registered_domain": "dns.question.registered_domain",
+ "dns-question-name": "dns.question.name",
+ "dns-answers-type": "dns.answers.type"
+}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json
index cf08cc696..e968d6451 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json
@@ -1,16 +1,8 @@
{
"properties": {
- "source_ip": {
+ "winlog.event_id": {
"type": "alias",
- "path": "src_ip"
- },
- "windows-event_data-CommandLine": {
- "path": "CommandLine",
- "type": "alias"
- },
- "event_uid": {
- "path": "EventID",
- "type": "alias"
+ "path": "EventID"
}
}
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_detector.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_detector.json
index 67eca1110..a17853598 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_detector.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_detector.json
@@ -20,14 +20,18 @@
"id": "1a4bd6e3-4c6e-405d-a9a3-53a116e341d4"
}
],
- "custom_rules": []
+ "custom_rules": [
+ {
+ "id": ""
+ }
+ ]
}
}
],
"triggers": [
{
"name": "sample_alert_condition",
- "sev_levels": [],
+ "sev_levels": ["high"],
"tags": [],
"actions": [
{
@@ -35,11 +39,11 @@
"name": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: sample_detector",
"destination_id": "",
"subject_template": {
- "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: sample_detector",
+ "source": "Triggered alert condition: - Severity: 1 (Highest) - Threat detector: sample_detector",
"lang": "mustache"
},
"message_template": {
- "source": "Triggered alert condition: \nSeverity: 1 (Highest)\nThreat detector: sample_detector\nDescription: Description for sample_detector.\nDetector data sources:\n\twindows",
+ "source": "Triggered alert condition: \nSeverity: 1 (Highest) \nThreat detector: sample_detector\nDescription: Description for sample_detector.\nDetector data sources:\n\twindows",
"lang": "mustache"
},
"throttle_enabled": false,
@@ -51,7 +55,7 @@
],
"types": ["windows"],
"severity": "4",
- "ids": ["1a4bd6e3-4c6e-405d-a9a3-53a116e341d4"]
+ "ids": []
}
]
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_dns_index_settings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_dns_index_settings.json
new file mode 100644
index 000000000..02b01e771
--- /dev/null
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_dns_index_settings.json
@@ -0,0 +1,21 @@
+{
+ "mappings": {
+ "properties": {
+ "dns.question.name": {
+ "type": "text"
+ },
+ "dns.answers.type": {
+ "type": "text"
+ },
+ "dns.question.registered_domain": {
+ "type": "text"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "number_of_shards": "1",
+ "number_of_replicas": "1"
+ }
+ }
+}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json
index d23b31895..9a03f3bb9 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json
@@ -1,39 +1,3 @@
{
- "EventTime": "2020-02-04T14:59:39.343541+00:00",
- "HostName": "EC2AMAZ-EPO7HKA",
- "Keywords": "9223372036854775808",
- "SeverityValue": 2,
- "Severity": "INFO",
- "EventID": 2003,
- "SourceName": "Microsoft-Windows-Sysmon",
- "ProviderGuid": "{5770385F-C22A-43E0-BF4C-06F5698FFBD9}",
- "Version": 5,
- "TaskValue": 22,
- "OpcodeValue": 0,
- "RecordNumber": 9532,
- "ExecutionProcessID": 1996,
- "ExecutionThreadID": 2616,
- "Channel": "Microsoft-Windows-Sysmon/Operational",
- "Domain": "NT AUTHORITY",
- "AccountName": "SYSTEM",
- "UserID": "S-1-5-18",
- "AccountType": "User",
- "Message": "Dns query:\r\nRuleName: \r\nUtcTime: 2020-02-04 14:59:38.349\r\nProcessGuid: {b3c285a4-3cda-5dc0-0000-001077270b00}\r\nProcessId: 1904\r\nQueryName: EC2AMAZ-EPO7HKA\r\nQueryStatus: 0\r\nQueryResults: 172.31.46.38;\r\nImage: C:\\Program Files\\nxlog\\nxlog.exe",
- "Category": "Dns query (rule: DnsQuery)",
- "Opcode": "Info",
- "UtcTime": "2020-02-04 14:59:38.349",
- "ProcessGuid": "{b3c285a4-3cda-5dc0-0000-001077270b00}",
- "ProcessId": "1904",
- "QueryName": "EC2AMAZ-EPO7HKA",
- "QueryStatus": "0",
- "QueryResults": "172.31.46.38;",
- "Image": "C:\\Program Files\\nxlog\\regsvr32.exe",
- "EventReceivedTime": "2020-02-04T14:59:40.780905+00:00",
- "SourceModuleName": "in",
- "SourceModuleType": "im_msvistalog",
- "CommandLine": "eachtest",
- "Initiated": "true",
- "Provider_Name": "Microsoft-Windows-Kernel-General",
- "TargetObject": "\\SOFTWARE\\Microsoft\\Office\\Outlook\\Security",
- "EventType": "SetValue"
+ "EventID": 2003
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_field_mappings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_field_mappings.json
index 6e8d728fe..593d42bb5 100644
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_field_mappings.json
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_field_mappings.json
@@ -1,26 +1,6 @@
{
"properties": {
- "windows-hostname": {
- "type": "alias",
- "path": "HostName"
- },
- "windows-message": {
- "type": "alias",
- "path": "Message"
- },
- "windows-provider-name": {
- "type": "alias",
- "path": "Provider_Name"
- },
- "windows-servicename": {
- "type": "alias",
- "path": "ServiceName"
- },
- "windows-event_data-CommandLine": {
- "path": "CommandLine",
- "type": "alias"
- },
- "event_uid": {
+ "winlog.event_id": {
"path": "EventID",
"type": "alias"
}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json
deleted file mode 100644
index a8a5294a7..000000000
--- a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "mappings": {
- "properties": {
- "CommandLine": {
- "type": "text"
- },
- "EventID": {
- "type": "integer"
- },
- "HostName": {
- "type": "text"
- },
- "Message": {
- "type": "text"
- },
- "Provider_Name": {
- "type": "text"
- },
- "ServiceName": {
- "type": "text"
- },
- "DnsQuestionName": {
- "type": "text"
- }
- }
- },
- "settings": {
- "index": {
- "number_of_shards": "1",
- "number_of_replicas": "1"
- }
- }
-}
diff --git a/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json
new file mode 100644
index 000000000..02c187caf
--- /dev/null
+++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json
@@ -0,0 +1,18 @@
+{
+ "mappings": {
+ "properties": {
+ "EventID": {
+ "type": "integer"
+ },
+ "winlog.provider_name": {
+ "type": "text"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "number_of_shards": "1",
+ "number_of_replicas": "1"
+ }
+ }
+}
diff --git a/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors.spec.js b/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors.spec.js
index 0d821420c..823d2f84e 100644
--- a/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors.spec.js
+++ b/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors.spec.js
@@ -3,171 +3,247 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import sample_index_settings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json';
-import dns_rule_data from '../../../fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule.json';
-import { BACKEND_BASE_PATH } from '../../../utils/base_constants';
import {
NODE_API,
OPENSEARCH_DASHBOARDS_URL,
} from '../../../utils/plugins/security-analytics-dashboards-plugin/constants';
+import sample_windows_index_settings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json';
+import sample_dns_index_settings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_dns_index_settings.json';
+import dns_name_rule_data from '../../../fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_name_selection.json';
+import dns_type_rule_data from '../../../fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_type_selection.json';
+import _ from 'lodash';
+import { BACKEND_BASE_PATH } from '../../../utils/base_constants';
-const testMappings = {
- properties: {
- 'dns-question-name': {
- type: 'alias',
- path: 'DnsQuestionName',
- },
- },
-};
+const cypressIndexDns = 'cypress-index-dns';
+const cypressIndexWindows = 'cypress-index-windows';
+const detectorName = 'test detector';
+const cypressLogTypeDns = 'dns';
-const cypressDNSRule = dns_rule_data.title;
+const cypressDNSRule = dns_name_rule_data.title;
-const createDetector = (detectorName, dataSource, expectFailure) => {
- // Locate Create detector button click to start
- cy.get('.euiButton')
- .filter(':contains("Create detector")')
- .click({ force: true });
+const getNameField = () =>
+ cy.sa_getInputByPlaceholder('Enter a name for the detector.');
- // Check to ensure process started
- cy.contains('Define detector');
+const getNextButton = () => cy.sa_getButtonByText('Next');
+const getCreateDetectorButton = () => cy.sa_getButtonByText('Create detector');
- // Enter a name for the detector in the appropriate input
- cy.get(`input[placeholder="Enter a name for the detector."]`)
- .focus()
- .realType(detectorName);
+const validateAlertPanel = (alertName) =>
+ cy
+ .sa_getElementByText('.euiTitle', 'Alert triggers')
+ .parentsUntil('.euiPanel')
+ .siblings()
+ .eq(2)
+ .within(() => cy.sa_getElementByText('button', alertName));
- // Select our pre-seeded data source (check cypressIndexDns)
- cy.get(`[data-test-subj="define-detector-select-data-source"]`)
- .find('input')
- .focus()
- .realType(dataSource);
-
- cy.intercept({
- pathname: NODE_API.RULES_SEARCH,
- query: {
- prePackaged: 'true',
- },
- }).as('getSigmaRules');
-
- // Select threat detector type (Windows logs)
- cy.get(`input[id="dns"]`).click({ force: true });
-
- cy.wait('@getSigmaRules').then(() => {
- // Open Detection rules accordion
- cy.get('[data-test-subj="detection-rules-btn"]').click({
- force: true,
- timeout: 5000,
- });
+const dataSourceLabel = 'Select or input source indexes or index patterns';
- cy.contains('table tr', 'DNS', {
- timeout: 120000,
- });
- });
+const getDataSourceField = () => cy.sa_getFieldByLabel(dataSourceLabel);
- // Check that correct page now showing
- cy.contains('Configure field mapping');
+const logTypeLabel = 'Select a log type you would like to detect';
- if (!expectFailure) {
- // Select appropriate names to map fields to
- for (let field_name in testMappings.properties) {
- const mappedTo = testMappings.properties[field_name].path;
+const getLogTypeField = () => cy.sa_getFieldByLabel(logTypeLabel);
- cy.contains('tr', field_name).within(() => {
- cy.get(`[data-test-subj="detector-field-mappings-select"]`)
- .click()
- .type(mappedTo);
+const openDetectorDetails = (detectorName) => {
+ cy.sa_getInputByPlaceholder('Search threat detectors')
+ .type(`${detectorName}`)
+ .sa_pressEnterKey();
+ cy.sa_getElementByText('.euiTableCellContent button', detectorName).click();
+};
+
+const getMappingFields = (properties, items = [], prefix = '') => {
+ for (let field in properties) {
+ const fullFieldName = prefix ? `${prefix}.${field}` : field;
+ const nextProperties = properties[field].properties;
+ if (!nextProperties) {
+ items.push({
+ ruleFieldName: fullFieldName,
+ logFieldName: properties[field].path,
});
+ } else {
+ getMappingFields(nextProperties, items, fullFieldName);
}
}
+ return items;
+};
- // Click Next button to continue
- cy.get('button').contains('Next').click({ force: true });
-
- // Check that correct page now showing
- cy.contains('Set up alert triggers');
-
- // Type name of new trigger
- cy.get(`input[placeholder="Enter a name to describe the alert condition"]`)
- .focus()
- .realType('test_trigger');
-
- // Type in (or select) tags for the alert condition
- cy.get(`[data-test-subj="alert-tags-combo-box"]`)
- .find('input')
- .focus()
- .realType('attack.defense_evasion')
- .realPress('Enter');
+const validateFieldMappingsTable = (message = '') => {
+ cy.wait('@getMappingsView').then((interception) => {
+ cy.wait(10000).then(() => {
+ cy.get('.reviewFieldMappings').should('be.visible');
+ const properties = interception.response.body.response.properties;
+ const unmapped_field_aliases =
+ interception.response.body.response.unmapped_field_aliases
+ .map((field) => [field])
+ .sort()
+ .slice(0, 10);
+
+ Cypress.log({
+ message: `Validate table data - ${message}`,
+ });
+ if (_.isEmpty(properties)) {
+ validatePendingFieldMappingsPanel(unmapped_field_aliases);
+ } else {
+ let items = getMappingFields(properties, [], '');
+ items = items.map((item) => [item.ruleFieldName, item.logFieldName]);
+ validateAutomaticFieldMappingsPanel(items);
+ }
+ });
+ });
+};
- // Select applicable severity levels
- cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true });
- cy.contains('1 (Highest)').click({ force: true });
+const editDetectorDetails = (detectorName, panelTitle) => {
+ cy.sa_urlShouldContain('detector-details').then(() => {
+ cy.sa_getElementByText('.euiTitle', detectorName);
+ cy.sa_getElementByText('.euiPanel .euiTitle', panelTitle);
+ cy.sa_getElementByText('.euiPanel .euiTitle', panelTitle)
+ .parent()
+ .siblings()
+ .within(() => cy.get('button').contains('Edit').click());
+ });
+};
- // Continue to next page
- cy.contains('Next').click({ force: true });
+const validateAutomaticFieldMappingsPanel = (mappings) =>
+ cy.get('.editFieldMappings').within(() => {
+ cy.get('.euiAccordion__triggerWrapper button').then(($btn) => {
+ cy.get($btn).contains(`Automatically mapped fields (${mappings.length})`);
- // Confirm page is reached
- cy.contains('Review and create');
+ // first check if the accordion is expanded, if not than expand the accordion
+ if ($btn[0].getAttribute('aria-expanded') === 'false') {
+ cy.get($btn[0])
+ .click()
+ .then(() => {
+ cy.sa_getElementByTestSubject('auto-mapped-fields-table')
+ .find('.euiBasicTable')
+ .sa_validateTable(mappings);
+ });
+ }
+ });
+ });
- // Confirm field mappings registered
- cy.contains('Field mapping');
+const validatePendingFieldMappingsPanel = (mappings) => {
+ cy.get('.editFieldMappings').within(() => {
+ // Pending field mappings
+ cy.sa_getElementByText('.euiTitle', 'Pending field mappings')
+ .parents('.euiPanel')
+ .within(() => {
+ cy.sa_getElementByTestSubject('pending-mapped-fields-table')
+ .find('.euiBasicTable')
+ .sa_validateTable(mappings);
+ });
+ });
+};
- if (!expectFailure) {
- for (let field in testMappings.properties) {
- const mappedTo = testMappings.properties[field].path;
+const fillDetailsForm = (detectorName, dataSource) => {
+ getNameField().type(detectorName);
+ getDataSourceField().sa_selectComboboxItem(dataSource);
+ getDataSourceField().focus().blur();
+ getLogTypeField().sa_selectComboboxItem(cypressLogTypeDns);
+ getLogTypeField().focus().blur();
+};
- cy.contains(field);
- cy.contains(mappedTo);
+const createDetector = (detectorName, dataSource, expectFailure) => {
+ getCreateDetectorButton().click({ force: true });
+
+ fillDetailsForm(detectorName, dataSource);
+
+ cy.sa_getElementByText(
+ '.euiAccordion .euiTitle',
+ 'Detection rules (14 selected)'
+ )
+ .click({ force: true, timeout: 5000 })
+ .then(() => cy.contains('.euiTable .euiTableRow', 'Dns'));
+
+ cy.sa_getElementByText('.euiAccordion .euiTitle', 'Field mapping - optional');
+ cy.get('[aria-controls="mappedTitleFieldsAccordion"]').then(($btn) => {
+ // first check if the accordion is expanded, if not than expand the accordion
+ if ($btn && $btn[0] && $btn[0].getAttribute('aria-expanded') === 'false') {
+ $btn[0].click();
}
- }
+ });
- // Confirm entries user has made
- cy.contains('Detector details');
- cy.contains(detectorName);
- cy.contains('dns');
- cy.contains('test_trigger');
+ // go to the alerts page
+ getNextButton().click({ force: true });
+
+ // TEST ALERTS PAGE
+ // Open the trigger details accordion
+ cy.get('[data-test-subj="trigger-details-btn"]').click({ force: true });
+ cy.sa_getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers');
+ cy.sa_getInputByPlaceholder(
+ 'Enter a name to describe the alert condition'
+ ).type('test_trigger');
+ cy.sa_getElementByTestSubject('alert-tags-combo-box')
+ .type(`attack.defense_evasion{enter}`)
+ .find('input')
+ .focus()
+ .blur();
- // Create the detector
- cy.get('button').contains('Create').click({ force: true });
- cy.contains(detectorName);
+ cy.sa_getFieldByLabel('Specify alert severity').sa_selectComboboxItem(
+ '1 (Highest)'
+ );
- cy.contains('Attempting to create the detector.');
+ cy.intercept('POST', NODE_API.MAPPINGS_BASE).as('createMappingsRequest');
+ cy.intercept('POST', NODE_API.DETECTORS_BASE).as('createDetectorRequest');
- // Confirm detector active
- cy.contains(detectorName);
- cy.contains('Active');
+ // create the detector
+ cy.sa_getElementByText('button', 'Create').click({ force: true });
+
+ // TEST DETECTOR DETAILS PAGE
+ cy.wait('@createMappingsRequest');
if (!expectFailure) {
- cy.contains('Actions');
+ cy.wait('@createDetectorRequest').then((interceptor) => {
+ const detectorId = interceptor.response.body.response._id;
+
+ cy.url()
+ .should('contain', detectorId)
+ .then(() => {
+ cy.sa_getElementByText(
+ '.euiCallOut',
+ `Detector created successfully: ${detectorName}`
+ );
+
+ // Confirm detector state
+ cy.sa_getElementByText('.euiTitle', detectorName);
+ cy.sa_getElementByText('.euiHealth', 'Active').then(() => {
+ cy.sa_validateDetailsItem('Detector name', detectorName);
+ cy.sa_validateDetailsItem('Description', '-');
+ cy.sa_validateDetailsItem('Detector schedule', 'Every 1 minute');
+ cy.sa_validateDetailsItem('Detection rules', '14');
+ cy.sa_validateDetailsItem(
+ 'Detector dashboard',
+ 'Not available for this log type'
+ );
+
+ cy.wait(5000); // waiting for the page to be reloaded after pushing detector id into route
+ cy.sa_getElementByText('button.euiTab', 'Alert triggers')
+ .should('be.visible')
+ .click();
+ validateAlertPanel('Trigger 1');
+ });
+ });
+ });
}
-
- cy.contains('Detector configuration');
- cy.contains('Field mappings');
- cy.contains('Alert triggers');
- cy.contains('Detector details');
- cy.contains('Created at');
- cy.contains('Last updated time');
};
-describe('Detectors', () => {
- const cypressIndexDns = 'cypress-index-dns';
- const cypressIndexWindows = 'cypress-index-windows';
- const detectorName = 'test detector';
+const openCreateForm = () => getCreateDetectorButton().click({ force: true });
+
+const getDescriptionField = () =>
+ cy.sa_getTextareaByLabel('Description - optional');
+const getTriggerNameField = () => cy.sa_getFieldByLabel('Trigger name');
+describe('Detectors', () => {
before(() => {
- cy.cleanUpTests();
+ cy.sa_cleanUpTests();
- cy.createIndex(cypressIndexWindows, null, sample_index_settings);
+ cy.sa_createIndex(cypressIndexWindows, sample_windows_index_settings);
// Create test index
- cy.createIndex(cypressIndexDns, null, sample_index_settings).then(() =>
+ cy.sa_createIndex(cypressIndexDns, sample_dns_index_settings).then(() =>
cy
- .request({
- method: 'POST',
- url: `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=true`,
- headers: {
- 'osd-xsrf': true,
- },
- body: {
+ .request(
+ 'POST',
+ `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=true`,
+ {
from: 0,
size: 5000,
query: {
@@ -178,240 +254,321 @@ describe('Detectors', () => {
},
},
},
- },
- })
+ }
+ )
.should('have.property', 'status', 200)
);
- cy.createRule(dns_rule_data);
+ cy.sa_createRule(dns_name_rule_data);
+ cy.sa_createRule(dns_type_rule_data);
});
- beforeEach(() => {
- cy.intercept(NODE_API.SEARCH_DETECTORS).as('detectorsSearch');
- // Visit Detectors page
- cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
- cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');
+ describe('...should validate form fields', () => {
+ beforeEach(() => {
+ cy.intercept(NODE_API.SEARCH_DETECTORS).as('detectorsSearch');
- // Check that correct page is showing
- cy.contains('Threat detectors');
- });
+ // Visit Detectors page before any test
+ cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
+ cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');
- it('...should show mappings warning', () => {
- // Locate Create detector button click to start
- cy.get('.euiButton')
- .filter(':contains("Create detector")')
- .click({ force: true });
-
- // Check to ensure process started
- cy.contains('Define detector');
-
- // Select our pre-seeded data source (check cypressIndexDns)
- cy.get(`[data-test-subj="define-detector-select-data-source"]`)
- .find('input')
- .focus()
- .realType(cypressIndexDns);
-
- // Select threat detector type (Windows logs)
- cy.get(`input[id="dns"]`).click({ force: true });
-
- // Select our pre-seeded data source (check cypressIndexDns)
- cy.get(`[data-test-subj="define-detector-select-data-source"]`)
- .find('input')
- .focus()
- .realType(cypressIndexWindows)
- .realPress('Enter');
-
- cy.get('.euiCallOut')
- .should('be.visible')
- .contains(
- 'To avoid issues with field mappings, we recommend creating separate detectors for different log types.'
- );
- });
-
- it('...can be created', () => {
- createDetector(detectorName, cypressIndexDns, false);
- cy.contains('Detector created successfully');
- });
-
- it('...can fail creation', () => {
- createDetector(`${detectorName}_fail`, '.kibana_1', true);
- cy.contains('Create detector failed.');
- });
+ openCreateForm();
+ });
- it('...basic details can be edited', () => {
- // Click on detector name
- cy.contains(detectorName).click({ force: true });
- cy.contains('Detector details');
- cy.contains(detectorName);
+ it('...should validate name field', () => {
+ getNameField().should('be.empty');
+ getNameField().focus().blur();
+ getNameField()
+ .parentsUntil('.euiFormRow__fieldWrapper')
+ .siblings()
+ .contains('Enter a name.');
+
+ getNameField().type('text').focus().blur();
+
+ getNameField()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .contains(
+ 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
+ );
+
+ getNameField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('tex&')
+ .focus()
+ .blur();
+
+ getNameField()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .contains(
+ 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
+ );
+
+ getNameField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Detector name')
+ .focus()
+ .blur()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
+ });
- // Click "Edit" button in detector details
- cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({
- force: true,
+ it('...should validate description field', () => {
+ const longDescriptionText =
+ 'This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.';
+
+ getDescriptionField().should('be.empty');
+
+ getDescriptionField().type(longDescriptionText).focus().blur();
+
+ getDescriptionField()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .contains(
+ 'Description should only consist of upper and lowercase letters, numbers 0-9, commas, hyphens, periods, spaces, and underscores. Max limit of 500 characters.'
+ );
+
+ getDescriptionField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Detector description...')
+ .focus()
+ .blur();
+
+ getDescriptionField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Detector name')
+ .focus()
+ .blur()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
});
- // Confirm arrival at "Edit detector details" page
- cy.contains('Edit detector details');
-
- // Change detector name
- cy.get(`input[placeholder="Enter a name for the detector."]`)
- .realClick()
- .ospClear()
- .realType('test detector edited');
-
- // Change detector description
- cy.get(`[data-test-subj="define-detector-detector-description"]`)
- .focus()
- .realType('Edited description');
-
- // Change input source
- cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true });
- cy.get(`[data-test-subj="define-detector-select-data-source"]`)
- .realType(cypressIndexWindows)
- .realPress('Enter');
-
- // Change detector scheduling
- cy.get(`[data-test-subj="detector-schedule-number-select"]`)
- .ospClear()
- .focus()
- .realType('10');
- cy.get(`[data-test-subj="detector-schedule-unit-select"]`).select('Hours');
-
- // Save changes to detector details
- cy.get(`[data-test-subj="save-basic-details-edits"]`).click({
- force: true,
+ it('...should validate data source field', () => {
+ getDataSourceField()
+ .focus()
+ .blur()
+ .parentsUntil('.euiFormRow__fieldWrapper')
+ .siblings()
+ .contains('Select an input source.');
+
+ getDataSourceField().sa_selectComboboxItem(cypressIndexDns);
+ getDataSourceField()
+ .focus()
+ .blur()
+ .parentsUntil('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
});
- // Confirm taken to detector details page
- cy.contains(detectorName);
+ it('...should validate next button', () => {
+ getNextButton().should('be.disabled');
- // Verify edits are applied
- cy.contains('test detector edited');
- cy.contains('Every 10 hours');
- cy.contains('Edited description');
- cy.contains(cypressIndexWindows);
- });
+ fillDetailsForm(detectorName, cypressIndexDns);
+ getNextButton().should('be.enabled');
+ });
- it('...rules can be edited', () => {
- // Ensure start on main detectors page
- cy.contains('Threat detectors');
+ it('...should validate alerts page', () => {
+ fillDetailsForm(detectorName, cypressIndexDns);
+ getNextButton().click({ force: true });
+ // Open the trigger details accordion
+ cy.get('[data-test-subj="trigger-details-btn"]').click({ force: true });
+ getTriggerNameField().should('have.value', 'Trigger 1');
+
+ getTriggerNameField()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
+
+ getCreateDetectorButton().should('be.enabled');
+
+ getTriggerNameField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .focus()
+ .blur();
+ getCreateDetectorButton().should('be.disabled');
+
+ cy.sa_getButtonByText('Remove').click({ force: true });
+ getCreateDetectorButton().should('be.enabled');
+ });
- // Click on detector name
- cy.contains(detectorName).click({ force: true });
- cy.contains('Detector details');
- cy.contains(detectorName);
+ it('...should show mappings warning', () => {
+ fillDetailsForm(detectorName, cypressIndexDns);
- // Confirm number of rules before edit
- cy.contains('Active rules (13)');
+ getDataSourceField().sa_selectComboboxItem(cypressIndexWindows);
+ getDataSourceField().focus().blur();
- // Click "Edit" button in Detector rules panel
- cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true });
+ cy.get('[data-test-subj="define-detector-diff-log-types-warning"]')
+ .should('be.visible')
+ .contains(
+ 'To avoid issues with field mappings, we recommend creating separate detectors for different log types.'
+ );
+ });
+ });
- // Confirm arrival on "Edit detector rules" page
- cy.contains('Edit detector rules');
+ describe('...validate create detector flow', () => {
+ beforeEach(() => {
+ cy.intercept(NODE_API.SEARCH_DETECTORS)
+ .as('detectorsSearch')
+ .as('detectorsSearch');
- // Search for specific rule
- cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule);
+ // Visit Detectors page before any test
+ cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
+ cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');
+ });
- // Toggle single search result to unchecked
- cy.contains('table tr', cypressDNSRule).within(() => {
- // Of note, timeout can sometimes work instead of wait here, but is very unreliable from case to case.
- cy.wait(1000);
- cy.get('button').eq(1).click({ force: true });
+ it('...can fail creation', () => {
+ createDetector(`${detectorName}_fail`, '.kibana_1', true);
+ cy.sa_getElementByText('.euiCallOut', 'Create detector failed.');
});
- // Save changes
- cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({
- force: true,
+ it('...can be created', () => {
+ createDetector(detectorName, cypressIndexDns, false);
+ cy.sa_getElementByText('.euiCallOut', 'Detector created successfully');
});
- // Confirm 1 rule has been removed from detector
- cy.contains('Active rules (12)');
+ it('...basic details can be edited', () => {
+ cy.intercept('GET', NODE_API.INDICES_BASE).as('getIndices');
+ openDetectorDetails(detectorName);
- // Click "Edit" button in Detector rules panel
- cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true });
+ editDetectorDetails(detectorName, 'Detector details');
- // Confirm arrival on "Edit detector rules" page
- cy.contains('Edit detector rules');
+ cy.sa_urlShouldContain('edit-detector-details').then(() => {
+ cy.sa_getElementByText('.euiTitle', 'Edit detector details');
+ });
- // Search for specific rule
- cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule);
+ cy.wait('@getIndices');
+ getNameField()
+ .type('{selectall}{backspace}')
+ .type('test detector edited');
+ cy.sa_getTextareaByLabel('Description - optional').type(
+ 'Edited description'
+ );
- // Toggle single search result to checked
- cy.contains('table tr', cypressDNSRule).within(() => {
- cy.wait(2000);
- cy.get('button').eq(1).click({ force: true });
- });
+ getDataSourceField().sa_clearCombobox();
+ getDataSourceField().sa_selectComboboxItem(cypressIndexWindows);
+
+ cy.sa_getFieldByLabel('Run every')
+ .type('{selectall}{backspace}')
+ .type('10');
+ cy.sa_getFieldByLabel('Run every', 'select').select('Hours');
+
+ cy.sa_getElementByText('button', 'Save changes').click({ force: true });
- // Save changes
- cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({
- force: true,
+ cy.sa_urlShouldContain('detector-details').then(() => {
+ cy.sa_validateDetailsItem('Detector name', 'test detector edited');
+ cy.sa_validateDetailsItem('Description', 'Edited description');
+ cy.sa_validateDetailsItem('Detector schedule', 'Every 10 hours');
+ cy.sa_validateDetailsItem('Data source', cypressIndexWindows);
+ });
});
- cy.contains(detectorName);
- // Confirm 1 rule has been added to detector
- cy.contains('Active rules (13)');
- });
+ it('...rules can be edited', () => {
+ openDetectorDetails(detectorName);
- it('...should update field mappings if data source is changed', () => {
- // Click on detector name
- cy.contains(detectorName).click({ force: true });
- cy.contains('Detector details');
- cy.contains(detectorName);
+ editDetectorDetails(detectorName, 'Active rules');
+ cy.sa_getElementByText('.euiTitle', 'Detection rules (14)');
- // Click "Edit" button in detector details
- cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({
- force: true,
- });
+ cy.sa_getInputByPlaceholder('Search...')
+ .type(`${cypressDNSRule}`)
+ .sa_pressEnterKey();
- // Confirm arrival at "Edit detector details" page
- cy.contains('Edit detector details');
+ cy.sa_getElementByText('.euiTableCellContent button', cypressDNSRule)
+ .parents('td')
+ .prev()
+ .find('.euiTableCellContent button')
+ .click();
- cy.get('.reviewFieldMappings').should('not.exist');
+ cy.sa_getElementByText('.euiTitle', 'Detection rules (13)');
+ cy.sa_getElementByText('button', 'Save changes').click({ force: true });
+ cy.sa_urlShouldContain('detector-details').then(() => {
+ cy.sa_getElementByText('.euiTitle', detectorName);
+ cy.sa_getElementByText('.euiPanel .euiTitle', 'Active rules (13)');
+ });
+ });
- // Change input source
- cy.get('.euiBadge__iconButton > .euiIcon').click({ force: true });
- cy.get(`[data-test-subj="define-detector-select-data-source"]`)
- .type(cypressIndexWindows)
- .realPress('Enter');
- });
+ xit('...should update field mappings if data source is changed', () => {
+ cy.intercept(
+ `${NODE_API.MAPPINGS_VIEW}?indexName=cypress-index-dns&ruleTopic=dns`
+ ).as('getMappingsView');
+ cy.intercept('GET', NODE_API.INDICES_BASE).as('getIndices');
+ openDetectorDetails(detectorName);
- it('...should update field mappings if rule selection is changed', () => {
- // Click on detector name
- cy.contains(detectorName).click({ force: true });
- cy.contains('Detector details');
- cy.contains(detectorName);
+ editDetectorDetails(detectorName, 'Detector details');
- // Click "Edit" button in detector details
- cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true });
+ cy.sa_urlShouldContain('edit-detector-details').then(() => {
+ cy.sa_getElementByText('.euiTitle', 'Edit detector details');
+ });
- // Confirm arrival at "Edit detector details" page
- cy.contains('Edit detector rules');
+ cy.wait('@getIndices');
+ cy.get('.reviewFieldMappings').should('not.exist');
- cy.get('.reviewFieldMappings').should('not.exist');
+ getDataSourceField().sa_clearCombobox();
+ getDataSourceField().should('not.have.value');
+ getDataSourceField().type(`${cypressIndexDns}{enter}`);
- cy.intercept(NODE_API.MAPPINGS_VIEW).as('getMappingsView');
+ validateFieldMappingsTable('data source is changed');
- cy.get('table th').within(() => {
- cy.get('button').first().click({ force: true });
+ cy.sa_getElementByText('button', 'Save changes').click({ force: true });
});
- cy.get('.reviewFieldMappings').should('be.visible');
- });
+ xit('...should show field mappings if rule selection is changed', () => {
+ cy.intercept(
+ `${NODE_API.MAPPINGS_VIEW}?indexName=cypress-index-windows&ruleTopic=dns`
+ ).as('getMappingsView');
+
+ openDetectorDetails(detectorName);
+
+ editDetectorDetails(detectorName, 'Active rules');
+
+ cy.sa_urlShouldContain('edit-detector-rules').then(() => {
+ cy.sa_getElementByText('.euiTitle', 'Edit detector rules');
+ });
- it('...can be deleted', () => {
- // Click on detector to be removed
- cy.contains('test detector edited').click({ force: true });
+ cy.get('.reviewFieldMappings').should('not.exist');
- // Confirm page
- cy.contains('Detector details');
+ cy.wait('@detectorsSearch');
- // Click "Actions" button, the click "Delete"
- cy.get('button').contains('Actions').click({ force: true });
- cy.get('button').contains('Delete').click({ force: true });
+ // Toggle single search result to unchecked
+ cy.get(
+ '[data-test-subj="edit-detector-rules-table"] table thead tr:first th:first button'
+ ).click({ force: true });
- // Confirm detector is deleted
- cy.contains('There are no existing detectors');
+ validateFieldMappingsTable('rules are changed');
+ });
+
+ it('...can be deleted', () => {
+ cy.intercept(`${NODE_API.RULES_BASE}/_search?prePackaged=true`).as(
+ 'getSigmaRules'
+ );
+ cy.intercept(`${NODE_API.RULES_BASE}/_search?prePackaged=false`).as(
+ 'getCustomRules'
+ );
+ openDetectorDetails(detectorName);
+
+ cy.wait('@detectorsSearch');
+ cy.wait('@getCustomRules');
+ cy.wait('@getSigmaRules');
+
+ cy.sa_getButtonByText('Actions')
+ .click({ force: true })
+ .then(() => {
+ cy.intercept(`${NODE_API.DETECTORS_BASE}/_search`).as('detectors');
+ cy.sa_getElementByText('.euiContextMenuItem', 'Delete').click({
+ force: true,
+ });
+ cy.wait('@detectors').then(() => {
+ cy.contains('There are no existing detectors');
+ });
+ });
+ });
});
- after(() => cy.cleanUpTests());
+ after(() => cy.sa_cleanUpTests());
});
diff --git a/cypress/integration/plugins/security-analytics-dashboards-plugin/2_rules.spec.js b/cypress/integration/plugins/security-analytics-dashboards-plugin/2_rules.spec.js
index 396e48d56..4b6fa5774 100644
--- a/cypress/integration/plugins/security-analytics-dashboards-plugin/2_rules.spec.js
+++ b/cypress/integration/plugins/security-analytics-dashboards-plugin/2_rules.spec.js
@@ -13,16 +13,13 @@ const SAMPLE_RULE = {
name: `Cypress test rule ${uniqueId}`,
logType: 'windows',
description: 'This is a rule used to test the rule creation workflow.',
- detection:
- 'selection:\n Provider_Name: Service Control Manager\nEventID: 7045\nServiceName: ZzNetSvc\n{backspace}{backspace}condition: selection',
detectionLine: [
- 'selection:',
- 'Provider_Name: Service Control Manager',
- 'EventID: 7045',
- 'ServiceName: ZzNetSvc',
- 'condition: selection',
+ 'condition: Selection_1',
+ 'Selection_1:',
+ 'FieldKey|contains:',
+ '- FieldValue',
],
- severity: 'critical',
+ severity: 'Critical',
tags: [
'attack.persistence',
'attack.privilege_escalation',
@@ -46,21 +43,18 @@ const YAML_RULE_LINES = [
`- ${SAMPLE_RULE.tags[2]}`,
`falsepositives:`,
`- ${SAMPLE_RULE.falsePositive}`,
- `level: ${SAMPLE_RULE.severity}`,
+ `level: ${SAMPLE_RULE.severity.toLowerCase()}`,
`status: ${SAMPLE_RULE.status}`,
`references:`,
`- '${SAMPLE_RULE.references}'`,
`author: ${SAMPLE_RULE.author}`,
`detection:`,
- ...SAMPLE_RULE.detection
- .replaceAll(' ', '')
- .replaceAll('{backspace}', '')
- .split('\n'),
+ ...SAMPLE_RULE.detectionLine,
];
const checkRulesFlyout = () => {
// Search for the rule
- cy.get(`input[placeholder="Search rules"]`).ospSearch(SAMPLE_RULE.name);
+ cy.get(`input[placeholder="Search rules"]`).sa_ospSearch(SAMPLE_RULE.name);
// Click the rule link to open the details flyout
cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({
@@ -96,7 +90,7 @@ const checkRulesFlyout = () => {
// Validate severity
cy.get('[data-test-subj="rule_flyout_rule_severity"]').contains(
- SAMPLE_RULE.severity
+ SAMPLE_RULE.severity.toLowerCase()
);
// Validate tags
@@ -156,205 +150,546 @@ const checkRulesFlyout = () => {
});
};
-describe('Rules', () => {
- before(() => cy.cleanUpTests());
- beforeEach(() => {
- cy.intercept({
- pathname: NODE_API.RULES_SEARCH,
- query: {
- prePackaged: 'true',
- },
- }).as('rulesSearch');
- // Visit Rules page
- cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/rules`);
- cy.wait('@rulesSearch').should('have.property', 'state', 'Complete');
-
- // Check that correct page is showing
- cy.contains('Rules');
+const getCreateButton = () => cy.get('[data-test-subj="create_rule_button"]');
+const getNameField = () => cy.sa_getFieldByLabel('Rule name');
+const getRuleStatusField = () => cy.sa_getFieldByLabel('Rule Status');
+const getDescriptionField = () =>
+ cy.sa_getFieldByLabel('Description - optional');
+const getAuthorField = () => cy.sa_getFieldByLabel('Author');
+const getLogTypeField = () => cy.sa_getFieldByLabel('Log type');
+const getRuleLevelField = () => cy.sa_getFieldByLabel('Rule level (severity)');
+const getSelectionPanelByIndex = (index) =>
+ cy.get(`[data-test-subj="detection-visual-editor-${index}"]`);
+const getSelectionNameField = () => cy.get('[data-test-subj="selection_name"]');
+const getMapKeyField = () =>
+ cy.get('[data-test-subj="selection_field_key_name"]');
+const getMapValueField = () =>
+ cy.get('[data-test-subj="selection_field_value"]');
+const getMapListField = () => cy.get('[data-test-subj="selection_field_list"]');
+const getListRadioField = () => cy.get('[for="selection-map-list-0-0"]');
+const getTextRadioField = () => cy.get('[for="selection-map-value-0-0"]');
+const getConditionField = () =>
+ cy.get('[data-test-subj="rule_detection_field"]');
+const getConditionAddButton = () =>
+ cy.get('[data-test-subj="condition-add-selection-btn"]');
+const getConditionRemoveButton = (index) =>
+ cy.get(`[data-test-subj="selection-exp-field-item-remove-${index}"]`);
+const getRuleSubmitButton = () =>
+ cy.get('[data-test-subj="submit_rule_form_button"]');
+const getTagField = (index) =>
+ cy.get(`[data-test-subj="rule_tags_field_${index}"]`);
+const getReferenceFieldByIndex = (index) =>
+ cy.get(`[data-test-subj="rule_references_field_${index}"]`);
+const getFalsePositiveFieldByIndex = (index) =>
+ cy.get(`[data-test-subj="rule_false_positives_field_${index}"]`);
+
+const toastShouldExist = () => {
+ submitRule();
+ cy.get('.euiToast').contains('Failed to create rule:');
+};
+
+const submitRule = () => getRuleSubmitButton().click({ force: true });
+const fillCreateForm = () => {
+ // rule overview
+ getNameField().type(SAMPLE_RULE.name);
+ getDescriptionField().type(SAMPLE_RULE.description);
+ getAuthorField().type(`${SAMPLE_RULE.author}`);
+
+ // rule details
+ getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType);
+ getRuleLevelField().sa_selectComboboxItem(SAMPLE_RULE.severity);
+
+ // rule detection
+ getSelectionPanelByIndex(0).within(() => {
+ getSelectionNameField().should('have.value', 'Selection_1');
+ getMapKeyField().type('FieldKey');
+
+ getTextRadioField().click({ force: true });
+ getMapValueField().type('FieldValue');
});
- it('...can be created', () => {
- // Click "create new rule" button
- cy.get('[data-test-subj="create_rule_button"]').click({
- force: true,
+ getConditionAddButton().click({ force: true });
+
+ // rule additional details
+ SAMPLE_RULE.tags.forEach((tag, idx) => {
+ getTagField(idx).type(tag);
+ idx < SAMPLE_RULE.tags.length - 1 &&
+ cy.sa_getButtonByText('Add tag').click({ force: true });
+ });
+
+ getReferenceFieldByIndex(0).type(SAMPLE_RULE.references);
+ getFalsePositiveFieldByIndex(0).type(SAMPLE_RULE.falsePositive);
+};
+
+describe('Rules', () => {
+ before(() => cy.sa_cleanUpTests());
+
+ describe('...should validate form fields', () => {
+ beforeEach(() => {
+ cy.intercept(`${NODE_API.RULES_BASE}/_search?prePackaged=true`).as(
+ 'rulesSearch'
+ );
+ // Visit Rules page
+ cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/rules`);
+ cy.wait('@rulesSearch').should('have.property', 'state', 'Complete');
+
+ // Check that correct page is showing
+ cy.sa_waitForPageLoad('rules', {
+ contains: 'Detection rules',
+ });
+
+ getCreateButton().click({ force: true });
});
- // Enter the log type
- cy.get('[data-test-subj="rule_status_dropdown"]').type(SAMPLE_RULE.status);
-
- // Enter the name
- cy.get('[data-test-subj="rule_name_field"]').type(SAMPLE_RULE.name);
-
- // Enter the log type
- cy.get('[data-test-subj="rule_type_dropdown"]').type(SAMPLE_RULE.logType);
-
- // Enter the description
- cy.get('[data-test-subj="rule_description_field"]').type(
- SAMPLE_RULE.description
- );
-
- // Enter the severity
- cy.get('[data-test-subj="rule_severity_dropdown"]').type(
- SAMPLE_RULE.severity
- );
-
- // Enter the tags
- SAMPLE_RULE.tags.forEach((tag) =>
- cy.get('[data-test-subj="rule_tags_dropdown"]').type(`${tag}{enter}`)
- );
-
- // Enter the reference
- cy.contains('Add another URL').click();
- cy.get('[data-test-subj="rule_references_field_0"]').type(
- SAMPLE_RULE.references
- );
-
- // Enter the false positive cases
- cy.get('[data-test-subj="rule_false_positives_field_0"]').type(
- `${SAMPLE_RULE.falsePositive}{enter}`
- );
-
- // Enter the author
- cy.get('[data-test-subj="rule_author_field"]').type(
- `${SAMPLE_RULE.author}{enter}`
- );
-
- // Enter the detection
- cy.get('[data-test-subj="rule_detection_field"] textarea').type(
- SAMPLE_RULE.detection,
- {
- force: true,
- }
- );
+ it('...should validate rule name', () => {
+ getNameField().sa_containsHelperText(
+ 'Rule name must contain 5-50 characters. Valid characters are a-z, A-Z, 0-9, hyphens, spaces, and underscores'
+ );
- // Switch to YAML editor
- cy.get('[data-test-subj="change-editor-type"] label:nth-child(2)').click({
- force: true,
+ getNameField().should('be.empty');
+ getNameField().focus().blur();
+ getNameField().sa_containsError('Rule name is required');
+ getNameField().type('text').focus().blur();
+ getNameField().sa_containsError('Invalid rule name.');
+
+ getNameField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('tex&')
+ .focus()
+ .blur();
+ getNameField().sa_containsError('Invalid rule name.');
+
+ getNameField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Rule name')
+ .focus()
+ .blur()
+ .sa_shouldNotHaveError();
});
- YAML_RULE_LINES.forEach((line) =>
- cy.get('[data-test-subj="rule_yaml_editor"]').contains(line)
- );
+ it('...should validate rule description field', () => {
+ const longDescriptionText =
+ 'This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.';
+
+ getDescriptionField().should('be.empty');
+ getDescriptionField().type(longDescriptionText).focus().blur();
+
+ getDescriptionField()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .contains(
+ 'Description should only consist of upper and lowercase letters, numbers 0-9, commas, hyphens, periods, spaces, and underscores. Max limit of 500 characters.'
+ );
+
+ getDescriptionField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Detector description...')
+ .focus()
+ .blur();
+
+ getDescriptionField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Detector name')
+ .focus()
+ .blur()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
+ });
- cy.intercept({
- url: NODE_API.RULES_BASE,
- }).as('getRules');
+ it('...should validate author', () => {
+ getAuthorField().sa_containsHelperText(
+ 'Combine multiple authors separated with a comma'
+ );
- // Click "create" button
- cy.get('[data-test-subj="submit_rule_form_button"]').click({
- force: true,
+ getAuthorField().should('be.empty');
+ getAuthorField().focus().blur();
+ getAuthorField().sa_containsError('Author name is required');
+ getAuthorField().type('text').focus().blur();
+ getAuthorField().sa_containsError('Invalid author.');
+
+ getAuthorField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('tex&')
+ .focus()
+ .blur();
+ getAuthorField().sa_containsError('Invalid author.');
+
+ getAuthorField()
+ .type('{selectall}')
+ .type('{backspace}')
+ .type('Rule name')
+ .focus()
+ .blur()
+ .sa_shouldNotHaveError();
});
- cy.wait('@getRules');
+ it('...should validate log type field', () => {
+ getLogTypeField().should('be.empty');
+ getLogTypeField().focus().blur();
+ getLogTypeField().sa_containsError('Log type is required');
- cy.contains('Rules');
+ getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType);
+ getLogTypeField().focus().blur().sa_shouldNotHaveError();
+ });
- checkRulesFlyout();
- });
+ it('...should validate rule level field', () => {
+ getRuleLevelField().should('be.empty');
+ getRuleLevelField().focus().blur();
+ getRuleLevelField().sa_containsError('Rule level is required');
+
+ getRuleLevelField().sa_selectComboboxItem(SAMPLE_RULE.severity);
+ getRuleLevelField().focus().blur().sa_shouldNotHaveError();
+ });
+
+ it('...should validate rule status field', () => {
+ getRuleStatusField().sa_containsValue(SAMPLE_RULE.status);
+ getRuleStatusField().focus().blur().sa_shouldNotHaveError();
+
+ getRuleStatusField().sa_clearCombobox();
+ getRuleStatusField().focus().blur();
+ getRuleStatusField().sa_containsError('Rule status is required');
+ });
+
+ it('...should validate selection', () => {
+ getSelectionPanelByIndex(0).within(() => {
+ getSelectionNameField().should('have.value', 'Selection_1');
+ getSelectionNameField().sa_clearValue();
+ getSelectionNameField().focus().blur();
+ getSelectionNameField()
+ .parentsUntil('.euiFormRow__fieldWrapper')
+ .siblings()
+ .contains('Selection name is required');
+
+ getSelectionNameField().type('Selection_1');
+ getSelectionNameField()
+ .focus()
+ .blur()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
+ });
+ });
- it('...can be edited', () => {
- cy.contains('Rules');
+ it('...should validate selection map key field', () => {
+ getSelectionPanelByIndex(0).within(() => {
+ getMapKeyField().should('be.empty');
+ getMapKeyField().focus().blur();
+ getMapKeyField()
+ .parentsUntil('.euiFormRow__fieldWrapper')
+ .siblings()
+ .contains('Key name is required');
+
+ getMapKeyField().type('FieldKey');
+ getMapKeyField()
+ .focus()
+ .blur()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
+ });
+ });
- cy.get(`input[placeholder="Search rules"]`).ospSearch(SAMPLE_RULE.name);
- cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({
- force: true,
+ it('...should validate selection map value field', () => {
+ getSelectionPanelByIndex(0).within(() => {
+ getMapValueField().should('be.empty');
+ getMapValueField().focus().blur();
+ getMapValueField()
+ .parentsUntil('.euiFormRow__fieldWrapper')
+ .siblings()
+ .contains('Value is required');
+
+ getMapValueField().type('FieldValue');
+ getMapValueField()
+ .focus()
+ .blur()
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
+ });
});
- cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`)
- .find('button')
- .contains('Action')
- .click({ force: true })
- .then(() => {
- // Confirm arrival at detectors page
- cy.get('.euiPopover__panel')
- .find('button')
- .contains('Edit')
- .click({ force: true });
+ it('...should validate selection map list field', () => {
+ getSelectionPanelByIndex(0).within(() => {
+ getListRadioField().click({ force: true });
+ getMapListField().should('be.empty');
+ getMapListField().focus().blur();
+ getMapListField()
+ .parentsUntil('.euiFormRow')
+ .contains('Value is required');
+
+ getMapListField().type('FieldValue');
+ getMapListField()
+ .focus()
+ .blur()
+ .parents('.euiFormRow')
+ .find('.euiFormErrorText')
+ .should('not.exist');
});
+ });
- const ruleNameSelector = '[data-test-subj="rule_name_field"]';
- cy.get(ruleNameSelector).clear();
-
- SAMPLE_RULE.name += ' edited';
- cy.get(ruleNameSelector).type(SAMPLE_RULE.name);
- cy.get(ruleNameSelector).should('have.value', SAMPLE_RULE.name);
-
- // Enter the log type
- const logSelector = '[data-test-subj="rule_type_dropdown"]';
- cy.get(logSelector).within(() =>
- cy.get('.euiFormControlLayoutClearButton').click({ force: true })
- );
- SAMPLE_RULE.logType = 'dns';
- YAML_RULE_LINES[2] = `product: ${SAMPLE_RULE.logType}`;
- YAML_RULE_LINES[3] = `title: ${SAMPLE_RULE.name}`;
- cy.get(logSelector).type(SAMPLE_RULE.logType).type('{enter}');
- cy.get(logSelector).contains(SAMPLE_RULE.logType, {
- matchCase: false,
+ it('...should validate condition field', () => {
+ getConditionField().scrollIntoView();
+ getConditionField().find('.euiFormErrorText').should('not.exist');
+ getRuleSubmitButton().click({ force: true });
+ getConditionField()
+ .parents('.euiFormRow__fieldWrapper')
+ .contains('Condition is required');
+
+ getConditionAddButton().click({ force: true });
+ getConditionField().find('.euiFormErrorText').should('not.exist');
+
+ getConditionRemoveButton(0).click({ force: true });
+ getConditionField()
+ .parents('.euiFormRow__fieldWrapper')
+ .contains('Condition is required');
});
- const ruleDescriptionSelector = '[data-test-subj="rule_description_field"]';
- SAMPLE_RULE.description += ' edited';
- YAML_RULE_LINES[4] = `description: ${SAMPLE_RULE.description}`;
- cy.get(ruleDescriptionSelector).clear();
- cy.get(ruleDescriptionSelector).type(SAMPLE_RULE.description);
- cy.get(ruleDescriptionSelector).should(
- 'have.value',
- SAMPLE_RULE.description
- );
-
- // Click "create" button
- cy.get('[data-test-subj="submit_rule_form_button"]').click({
- force: true,
+ it('...should validate tag field', () => {
+ getTagField(0).should('be.empty');
+ getTagField(0).type('wrong.tag').focus().blur();
+ getTagField(0)
+ .parents('.euiFormRow__fieldWrapper')
+ .contains("Tags must start with 'attack.'");
+
+ getTagField(0).sa_clearValue().type('attack.tag');
+ getTagField(0)
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist');
});
- cy.contains('Rules');
+ it('...should validate form', () => {
+ toastShouldExist();
+ fillCreateForm();
+
+ // rule name field
+ getNameField().sa_clearValue();
+ toastShouldExist();
+ getNameField().type('Rule name');
+
+ // author field
+ getAuthorField().sa_clearValue();
+ toastShouldExist();
+ getAuthorField().type('John Doe');
+
+ // log field
+ getLogTypeField().sa_clearCombobox();
+ toastShouldExist();
+ getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType);
+
+ // severity field
+ getRuleLevelField().sa_clearCombobox();
+ toastShouldExist();
+ getRuleLevelField().sa_selectComboboxItem(SAMPLE_RULE.severity);
+
+ // status field
+ getRuleStatusField().sa_clearCombobox();
+ toastShouldExist();
+ getRuleStatusField().sa_selectComboboxItem(SAMPLE_RULE.status);
+
+ // selection name field
+ getSelectionPanelByIndex(0).within(() =>
+ getSelectionNameField().type('{selectall}').type('{backspace}')
+ );
+ toastShouldExist();
+ getSelectionPanelByIndex(0).within(() =>
+ getSelectionNameField().type('Selection_1')
+ );
+
+ // selection map key field
+ getSelectionPanelByIndex(0).within(() =>
+ getMapKeyField().type('{selectall}').type('{backspace}')
+ );
+ toastShouldExist();
+ getSelectionPanelByIndex(0).within(() =>
+ getMapKeyField().type('FieldKey')
+ );
+
+ // selection map value field
+ getSelectionPanelByIndex(0).within(() =>
+ getMapValueField().type('{selectall}').type('{backspace}')
+ );
+ toastShouldExist();
+ getSelectionPanelByIndex(0).within(() =>
+ getMapValueField().type('FieldValue')
+ );
+
+ // selection map list field
+ getSelectionPanelByIndex(0).within(() => {
+ getListRadioField().click({ force: true });
+ getMapListField().sa_clearValue();
+ });
+ toastShouldExist();
+ getSelectionPanelByIndex(0).within(() => {
+ getListRadioField().click({ force: true });
+ getMapListField().type('FieldValue');
+ });
+
+ // condition field
+ getConditionRemoveButton(0).click({ force: true });
+ toastShouldExist();
+ getConditionAddButton().click({ force: true });
- checkRulesFlyout();
+ // tags field
+ getTagField(0).sa_clearValue().type('wrong.tag');
+ toastShouldExist();
+ getTagField(0).sa_clearValue().type('attack.tag');
+ });
});
- it('...can be deleted', () => {
- cy.intercept(`${NODE_API.RULES_SEARCH}?prePackaged=true`, {
- delay: 5000,
- }).as('getPrePackagedRules');
+ describe('...should validate create rule flow', () => {
+ beforeEach(() => {
+ cy.intercept(`${NODE_API.RULES_BASE}/_search?prePackaged=false`).as(
+ 'rulesSearch'
+ );
+ // Visit Rules page
+ cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/rules`);
+ cy.wait('@rulesSearch').should('have.property', 'state', 'Complete');
+
+ // Check that correct page is showing
+ cy.sa_waitForPageLoad('rules', {
+ contains: 'Detection rules',
+ });
+ });
+
+ it('...can be created', () => {
+ getCreateButton().click({ force: true });
+
+ fillCreateForm();
+
+ // Switch to YAML editor
+ cy.get('[data-test-subj="change-editor-type"] label:nth-child(2)').click({
+ force: true,
+ });
+
+ YAML_RULE_LINES.forEach((line) =>
+ cy.get('[data-test-subj="rule_yaml_editor"]').contains(line)
+ );
- cy.intercept(`${NODE_API.RULES_SEARCH}?prePackaged=false`, {
- delay: 5000,
- }).as('getCustomRules');
+ cy.intercept({
+ url: `${NODE_API.RULES_BASE}/_search?prePackaged=false`,
+ }).as('getRules');
- cy.get(`input[placeholder="Search rules"]`).ospSearch(SAMPLE_RULE.name);
+ submitRule();
- // Click the rule link to open the details flyout
- cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({
- force: true,
+ cy.wait('@getRules');
+
+ cy.sa_waitForPageLoad('rules', {
+ contains: 'Detection rules',
+ });
+
+ checkRulesFlyout();
});
- cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`)
- .find('button')
- .contains('Action')
- .click({ force: true })
- .then(() => {
- // Confirm arrival at detectors page
- cy.get('.euiPopover__panel')
- .find('button')
- .contains('Delete')
- .click({ force: true })
- .then(() =>
- cy
- .get('.euiModalFooter > .euiButton')
- .contains('Delete')
- .click({ force: true })
- );
+ it('...can be edited', () => {
+ cy.sa_waitForPageLoad('rules', {
+ contains: 'Detection rules',
+ });
+
+ cy.get(`input[placeholder="Search rules"]`).sa_ospSearch(
+ SAMPLE_RULE.name
+ );
+ cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({
+ force: true,
+ });
+
+ cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`)
+ .find('button')
+ .contains('Action')
+ .click({ force: true })
+ .then(() => {
+ // Confirm arrival at detectors page
+ cy.get('.euiPopover__panel').find('button').contains('Edit').click();
+ });
- cy.wait('@getCustomRules');
- cy.wait('@getPrePackagedRules');
+ getNameField().clear();
- // Search for sample_detector, presumably deleted
- cy.wait(3000);
- cy.get(`input[placeholder="Search rules"]`).ospSearch(SAMPLE_RULE.name);
- // Click the rule link to open the details flyout
- cy.get('tbody').contains(SAMPLE_RULE.name).should('not.exist');
+ SAMPLE_RULE.name += ' edited';
+ getNameField().type(SAMPLE_RULE.name);
+ getNameField().should('have.value', SAMPLE_RULE.name);
+
+ getLogTypeField().sa_clearCombobox();
+ SAMPLE_RULE.logType = 'dns';
+ YAML_RULE_LINES[2] = `product: ${SAMPLE_RULE.logType}`;
+ YAML_RULE_LINES[3] = `title: ${SAMPLE_RULE.name}`;
+ getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType);
+ getLogTypeField()
+ .sa_containsValue(SAMPLE_RULE.logType)
+ .contains(SAMPLE_RULE.logType);
+
+ SAMPLE_RULE.description += ' edited';
+ YAML_RULE_LINES[4] = `description: ${SAMPLE_RULE.description}`;
+ getDescriptionField().clear();
+ getDescriptionField().type(SAMPLE_RULE.description);
+ getDescriptionField().should('have.value', SAMPLE_RULE.description);
+
+ cy.intercept({
+ url: `${NODE_API.RULES_BASE}/_search?prePackaged=false`,
+ }).as('getRules');
+
+ submitRule();
+
+ cy.sa_waitForPageLoad('rules', {
+ contains: 'Detection rules',
});
+
+ cy.wait('@getRules');
+
+ checkRulesFlyout();
+ });
+
+ it('...can be deleted', () => {
+ cy.intercept('POST', `${NODE_API.RULES_BASE}/_search?prePackaged=true`, {
+ delay: 5000,
+ }).as('getPrePackagedRules');
+
+ cy.intercept('POST', `${NODE_API.RULES_BASE}/_search?prePackaged=false`, {
+ delay: 5000,
+ }).as('getCustomRules');
+
+ cy.get(`input[placeholder="Search rules"]`).sa_ospSearch(
+ SAMPLE_RULE.name
+ );
+
+ // Click the rule link to open the details flyout
+ cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({
+ force: true,
+ });
+
+ cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`)
+ .find('button')
+ .contains('Action')
+ .click({ force: true })
+ .then(() => {
+ // Confirm arrival at detectors page
+ cy.get('.euiPopover__panel')
+ .find('button')
+ .contains('Delete')
+ .click()
+ .then(() =>
+ cy.get('.euiModalFooter > .euiButton').contains('Delete').click()
+ );
+
+ cy.wait(5000);
+ cy.wait('@getCustomRules');
+ cy.wait('@getPrePackagedRules');
+
+ // Search for sample_detector, presumably deleted
+ cy.wait(3000);
+ cy.get(`input[placeholder="Search rules"]`).sa_ospSearch(
+ SAMPLE_RULE.name
+ );
+ // Click the rule link to open the details flyout
+ cy.get('tbody').contains(SAMPLE_RULE.name).should('not.exist');
+ });
+ });
});
- after(() => cy.cleanUpTests());
+ after(() => cy.sa_cleanUpTests());
});
diff --git a/cypress/integration/plugins/security-analytics-dashboards-plugin/3_alerts.spec.js b/cypress/integration/plugins/security-analytics-dashboards-plugin/3_alerts.spec.js
index a4ab6400c..8812d5c28 100644
--- a/cypress/integration/plugins/security-analytics-dashboards-plugin/3_alerts.spec.js
+++ b/cypress/integration/plugins/security-analytics-dashboards-plugin/3_alerts.spec.js
@@ -5,92 +5,43 @@
import {
DETECTOR_TRIGGER_TIMEOUT,
- NODE_API,
OPENSEARCH_DASHBOARDS_URL,
+ NODE_API,
+ createDetector,
} from '../../../utils/plugins/security-analytics-dashboards-plugin/constants';
-import sample_index_settings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json';
-import sample_alias_mappings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json';
-import sample_detector from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_detector.json';
-import sample_document from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json';
-
-const testIndex = 'sample_alerts_spec_cypress_test_index';
-const testDetectorName = 'alerts_spec_cypress_test_detector';
-const testDetectorAlertCondition = `${testDetectorName} alert condition`;
-
-// Creating a unique detector JSON for this test spec
-const testDetector = {
- ...sample_detector,
- name: testDetectorName,
- inputs: [
- {
- detector_input: {
- ...sample_detector.inputs[0].detector_input,
- description: `Description for ${testDetectorName}`,
- indices: [testIndex],
- },
- },
- ],
- triggers: [
- {
- ...sample_detector.triggers[0],
- name: testDetectorAlertCondition,
- },
- ],
-};
-
-// The exact minutes/seconds for the start and last updated time will be difficult to predict,
-// but all of the alert time fields should all contain the date in this format.
-
-// Moment is not available in this repository, so refactored this variable to use Date.
-// const date = moment(moment.now()).format('MM/DD/YY');
-const now = new Date(Date.now());
-const month =
- now.getMonth() + 1 < 10 ? `0${now.getMonth() + 1}` : `${now.getMonth() + 1}`;
-const day = now.getDate() < 10 ? `0${now.getDate()}` : `${now.getDate()}`;
-const year = `${now.getFullYear()}`.substr(2);
-const date = `${month}/${day}/${year}`;
+import indexSettings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json';
+import aliasMappings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json';
+import indexDoc from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json';
+import ruleSettings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_windows_usb_rule.json';
+
+const indexName = 'test-index';
+const detectorName = 'test-detector';
+const alertName = `${detectorName} alert condition`;
+
+function getFormattedDate(date) {
+ let year = date.getFullYear() % 100;
+ let month = (1 + date.getMonth()).toString().padStart(2, '0');
+ let day = date.getDate().toString().padStart(2, '0');
+ return month + '/' + day + '/' + year;
+}
+
+const date = getFormattedDate(new Date(Date.now())); //moment(moment.now()).format('MM/DD/YY');
const docCount = 4;
+
+let testDetectorCfg;
+
describe('Alerts', () => {
before(() => {
- // Delete any pre-existing test detectors
- cy.cleanUpTests()
- // Create test index
- .then(() => cy.createIndex(testIndex, null, sample_index_settings))
-
- // Create field mappings
- .then(() =>
- cy.createAliasMappings(
- testIndex,
- testDetector.detector_type,
- sample_alias_mappings,
- true
- )
- )
-
- // Create test detector
- .then(() => cy.createDetector(testDetector))
-
- .then(() => {
- // Go to the detectors table page
- cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
-
- // Check that correct page is showing
- cy.contains('Threat detectors');
-
- // Filter table to only show the test detector
- cy.get(`input[type="search"]`).type(`${testDetector.name}{enter}`);
-
- // Confirm detector was created
- cy.get('tbody > tr').should(($tr) => {
- expect($tr, 'detector name').to.contain(testDetector.name);
- });
- });
-
- // Ingest documents to the test index
- for (let i = 0; i < docCount; i++) {
- cy.insertDocumentToIndex(testIndex, '', sample_document);
- }
+ testDetectorCfg = createDetector(
+ detectorName,
+ indexName,
+ indexSettings,
+ aliasMappings,
+ ruleSettings,
+ indexDoc,
+ 4
+ );
// Wait for the detector to execute
cy.wait(DETECTOR_TRIGGER_TIMEOUT);
@@ -98,16 +49,18 @@ describe('Alerts', () => {
beforeEach(() => {
// Visit Alerts table page
- cy.intercept(NODE_API.SEARCH_DETECTORS).as('detectorsSearch');
+ cy.intercept(`${NODE_API.DETECTORS_BASE}/_search`).as('detectorsSearch');
// Visit Detectors page
cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/alerts`);
cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');
// Wait for page to load
- cy.contains('Security alerts');
+ cy.sa_waitForPageLoad('alerts', {
+ contains: 'Security alerts',
+ });
// Filter table to only show alerts for the test detector
- cy.get(`input[type="search"]`).type(`${testDetector.name}{enter}`);
+ cy.get(`input[type="search"]`).type(`${testDetectorCfg.name}{enter}`);
// Adjust the date range picker to display alerts from today
cy.get(
@@ -128,7 +81,7 @@ describe('Alerts', () => {
// Confirm there are alerts created
cy.get('tbody > tr')
- .filter(`:contains(${testDetectorAlertCondition})`)
+ .filter(`:contains(${alertName})`)
.should('have.length', docCount);
});
@@ -136,10 +89,10 @@ describe('Alerts', () => {
// Confirm there is a row containing the expected values
cy.get('tbody > tr').should(($tr) => {
expect($tr, 'start time').to.contain(date);
- expect($tr, 'trigger name').to.contain(testDetector.triggers[0].name);
- expect($tr, 'detector name').to.contain(testDetector.name);
+ expect($tr, 'trigger name').to.contain(testDetectorCfg.triggers[0].name);
+ expect($tr, 'detector name').to.contain(testDetectorCfg.name);
expect($tr, 'status').to.contain('Active');
- expect($tr, 'severity').to.contain('4 (Low)');
+ expect($tr, 'severity').to.contain('1 (Highest)');
});
});
@@ -156,7 +109,7 @@ describe('Alerts', () => {
// Confirm alert condition name
cy.get(
'[data-test-subj="text-details-group-content-alert-trigger-name"]'
- ).contains(testDetector.triggers[0].name);
+ ).contains(testDetectorCfg.triggers[0].name);
// Confirm alert status
cy.get(
@@ -166,7 +119,7 @@ describe('Alerts', () => {
// Confirm alert severity
cy.get(
'[data-test-subj="text-details-group-content-alert-severity"]'
- ).contains('4 (Low)');
+ ).contains('1 (Highest)');
// Confirm alert start time is present
cy.get(
@@ -180,19 +133,19 @@ describe('Alerts', () => {
// Confirm alert detector name
cy.get('[data-test-subj="text-details-group-content-detector"]').contains(
- testDetector.name
+ testDetectorCfg.name
);
// Wait for the findings table to finish loading
cy.contains('Findings (1)');
- cy.contains('USB Device Plugged');
+ cy.contains('Cypress USB Rule');
// Confirm alert findings contain expected values
cy.get('tbody > tr').should(($tr) => {
expect($tr, `timestamp`).to.contain(date);
- expect($tr, `rule name`).to.contain('USB Device Plugged');
- expect($tr, `detector name`).to.contain(testDetector.name);
- expect($tr, `log type`).to.contain('Windows');
+ expect($tr, `rule name`).to.contain('Cypress USB Rule');
+ expect($tr, `detector name`).to.contain(testDetectorCfg.name);
+ expect($tr, `log type`).to.contain('System Activity: Windows');
});
// Close the flyout
@@ -216,7 +169,7 @@ describe('Alerts', () => {
cy.get('[data-test-subj="alert-details-flyout"]').within(() => {
// Wait for findings table to finish loading
- cy.contains('USB Device Plugged');
+ cy.contains('Cypress USB Rule');
// Click the details button for the first finding
cy.get('tbody > tr')
@@ -243,7 +196,7 @@ describe('Alerts', () => {
// Confirm finding detector name
cy.get(
'[data-test-subj="finding-details-flyout-detector-link"]'
- ).contains(testDetector.name);
+ ).contains(testDetectorCfg.name);
// Confirm there's only 1 rule details accordion
cy.get(
@@ -257,22 +210,22 @@ describe('Alerts', () => {
// Confirm the accordion button contains the expected name
cy.get(
'[data-test-subj="finding-details-flyout-rule-accordion-button"]'
- ).contains('USB Device Plugged');
+ ).contains('Cypress USB Rule');
// Confirm the accordion button contains the expected severity
cy.get(
'[data-test-subj="finding-details-flyout-rule-accordion-button"]'
- ).contains('Severity: Low');
+ ).contains('Severity: High');
// Confirm the rule name
cy.get(
- '[data-test-subj="finding-details-flyout-USB Device Plugged-details"]'
- ).contains('USB Device Plugged');
+ '[data-test-subj="finding-details-flyout-Cypress USB Rule-details"]'
+ ).contains('Cypress USB Rule');
// Confirm the rule severity
cy.get(
'[data-test-subj="finding-details-flyout-rule-severity"]'
- ).contains('Low');
+ ).contains('High');
// Confirm the rule category
cy.get(
@@ -282,16 +235,14 @@ describe('Alerts', () => {
// Confirm the rule description
cy.get(
'[data-test-subj="finding-details-flyout-rule-description"]'
- ).contains('Detects plugged USB devices');
+ ).contains('USB plugged-in rule');
// Confirm the rule tags
- ['low', 'windows', 'attack.initial_access', 'attack.t1200'].forEach(
- (tag) => {
- cy.get(
- '[data-test-subj="finding-details-flyout-rule-tags"]'
- ).contains(tag);
- }
- );
+ ['high', 'windows'].forEach((tag) => {
+ cy.get(
+ '[data-test-subj="finding-details-flyout-rule-tags"]'
+ ).contains(tag);
+ });
});
// Confirm the rule document ID is present
@@ -302,19 +253,13 @@ describe('Alerts', () => {
// Confirm the rule index
cy.get(
'[data-test-subj="finding-details-flyout-rule-document-index"]'
- ).contains(testIndex);
+ ).contains(indexName);
// Confirm the rule document matches
// The EuiCodeEditor used for this component stores each line of the JSON in an array of elements;
// so this test formats the expected document into an array of strings,
// and matches each entry with the corresponding element line.
- const document = JSON.stringify(
- JSON.parse(
- '{"EventTime":"2020-02-04T14:59:39.343541+00:00","HostName":"EC2AMAZ-EPO7HKA","Keywords":"9223372036854775808","SeverityValue":2,"Severity":"INFO","EventID":2003,"SourceName":"Microsoft-Windows-Sysmon","ProviderGuid":"{5770385F-C22A-43E0-BF4C-06F5698FFBD9}","Version":5,"TaskValue":22,"OpcodeValue":0,"RecordNumber":9532,"ExecutionProcessID":1996,"ExecutionThreadID":2616,"Channel":"Microsoft-Windows-Sysmon/Operational","Domain":"NT AUTHORITY","AccountName":"SYSTEM","UserID":"S-1-5-18","AccountType":"User","Message":"Dns query:\\r\\nRuleName: \\r\\nUtcTime: 2020-02-04 14:59:38.349\\r\\nProcessGuid: {b3c285a4-3cda-5dc0-0000-001077270b00}\\r\\nProcessId: 1904\\r\\nQueryName: EC2AMAZ-EPO7HKA\\r\\nQueryStatus: 0\\r\\nQueryResults: 172.31.46.38;\\r\\nImage: C:\\\\Program Files\\\\nxlog\\\\nxlog.exe","Category":"Dns query (rule: DnsQuery)","Opcode":"Info","UtcTime":"2020-02-04 14:59:38.349","ProcessGuid":"{b3c285a4-3cda-5dc0-0000-001077270b00}","ProcessId":"1904","QueryName":"EC2AMAZ-EPO7HKA","QueryStatus":"0","QueryResults":"172.31.46.38;","Image":"C:\\\\Program Files\\\\nxlog\\\\regsvr32.exe","EventReceivedTime":"2020-02-04T14:59:40.780905+00:00","SourceModuleName":"in","SourceModuleType":"im_msvistalog","CommandLine":"eachtest","Initiated":"true","Provider_Name":"Microsoft-Windows-Kernel-General","TargetObject":"\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\Outlook\\\\Security","EventType":"SetValue"}'
- ),
- null,
- 2
- );
+ const document = JSON.stringify(JSON.parse('{"EventID": 2003}'), null, 2);
const documentLines = document.split('\n');
cy.get('[data-test-subj="finding-details-flyout-rule-document"]')
.get('[class="euiCodeBlock__line"]')
@@ -350,7 +295,7 @@ describe('Alerts', () => {
cy.get('[data-test-subj="alert-details-flyout"]').within(() => {
cy.get(
'[data-test-subj="text-details-group-content-alert-trigger-name"]'
- ).contains(testDetector.triggers[0].name);
+ ).contains(testDetectorCfg.triggers[0].name);
});
});
@@ -369,6 +314,11 @@ describe('Alerts', () => {
.within(() => {
cy.get('[class="euiCheckbox__input"]').click({ force: true });
});
+ cy.get('tbody > tr')
+ .last()
+ .within(() => {
+ cy.get('[class="euiCheckbox__input"]').click({ force: true });
+ });
// Press the "Acknowledge" button
cy.get('[data-test-subj="acknowledge-button"]').click({ force: true });
@@ -384,21 +334,28 @@ describe('Alerts', () => {
// Confirm there is an "Acknowledged" alert
cy.get('tbody > tr').should(($tr) => {
- expect($tr, `alert name`).to.contain(testDetectorAlertCondition);
+ expect($tr, `alert name`).to.contain(alertName);
expect($tr, `status`).to.contain('Acknowledged');
});
+ // Confirm there are now 2 "Acknowledged" alerts
+ cy.get('tbody > tr')
+ .filter(`:contains(${alertName})`)
+ .should('have.length', 2);
+
// Filter the table to show only "Active" alerts
- cy.get('[data-text="Status"]');
cy.get('[class="euiFilterSelect__items"]').within(() => {
cy.contains('Acknowledged').click({ force: true });
+ cy.contains('Active').click({ force: true });
});
// Confirm there are now 2 "Acknowledged" alerts
cy.get('tbody > tr')
- .filter(`:contains(${testDetectorAlertCondition})`)
- .should('contain', 'Active')
- .should('contain', 'Acknowledged');
+ .filter(`:contains(${alertName})`)
+ .should('contain', 'Active');
+ cy.get('tbody > tr')
+ .filter(`:contains(${alertName})`)
+ .should('have.length', 2);
});
it('can be acknowledged via row button', () => {
@@ -409,8 +366,8 @@ describe('Alerts', () => {
});
cy.get('tbody > tr')
- .filter(`:contains(${testDetectorAlertCondition})`)
- .should('have.length', 3);
+ .filter(`:contains(${alertName})`)
+ .should('have.length', 2);
cy.get('tbody > tr')
// Click the "Acknowledge" icon button in the first row
@@ -420,11 +377,10 @@ describe('Alerts', () => {
});
cy.get('tbody > tr')
- .filter(`:contains(${testDetectorAlertCondition})`)
- .should('have.length', 2);
+ .filter(`:contains(${alertName})`)
+ .should('have.length', 1);
// Filter the table to show only "Acknowledged" alerts
- cy.get('[data-text="Status"]');
cy.get('[class="euiFilterSelect__items"]').within(() => {
cy.contains('Active').click({ force: true });
cy.contains('Acknowledged').click({ force: true });
@@ -432,8 +388,8 @@ describe('Alerts', () => {
// Confirm there are now 3 "Acknowledged" alerts
cy.get('tbody > tr')
- .filter(`:contains(${testDetectorAlertCondition})`)
- .should('have.length', 2);
+ .filter(`:contains(${alertName})`)
+ .should('have.length', 3);
});
it('can be acknowledged via flyout button', () => {
@@ -484,7 +440,7 @@ describe('Alerts', () => {
cy.get('[data-test-subj="alert-details-flyout"]').within(() => {
// Wait for findings table to finish loading
- cy.contains('USB Device Plugged');
+ cy.contains('Cypress USB Rule');
// Click the details button for the first finding
cy.get('tbody > tr')
@@ -506,9 +462,9 @@ describe('Alerts', () => {
// Confirm the detector details page is for the expected detector
cy.get('[data-test-subj="detector-details-detector-name"]').contains(
- testDetector.name
+ testDetectorCfg.name
);
});
- after(() => cy.cleanUpTests());
+ after(() => cy.sa_cleanUpTests());
});
diff --git a/cypress/integration/plugins/security-analytics-dashboards-plugin/4_findings.spec.js b/cypress/integration/plugins/security-analytics-dashboards-plugin/4_findings.spec.js
index 60e255f9a..f692a6b52 100644
--- a/cypress/integration/plugins/security-analytics-dashboards-plugin/4_findings.spec.js
+++ b/cypress/integration/plugins/security-analytics-dashboards-plugin/4_findings.spec.js
@@ -4,34 +4,34 @@
*/
import {
+ createDetector,
DETECTOR_TRIGGER_TIMEOUT,
- NODE_API,
OPENSEARCH_DASHBOARDS_URL,
} from '../../../utils/plugins/security-analytics-dashboards-plugin/constants';
-import sample_document from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json';
-import sample_index_settings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json';
-import sample_field_mappings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_field_mappings.json';
-import sample_detector from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_detector.json';
+import indexSettings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json';
+import aliasMappings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_alias_mappings.json';
+import indexDoc from '../../../fixtures/plugins/security-analytics-dashboards-plugin/sample_document.json';
+import ruleSettings from '../../../fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_windows_usb_rule.json';
+
+const indexName = 'test-index';
+const detectorName = 'test-detector';
+const ruleName = 'Cypress USB Rule';
describe('Findings', () => {
- const ruleTags = ['low', 'windows'];
- const indexName = 'cypress-test-windows';
+ const ruleTags = ['high', 'windows'];
before(() => {
- cy.cleanUpTests();
-
- // Visit Findings page
- cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/findings`);
-
- // create test index, mappings, and detector
- cy.createIndex(indexName, null, sample_index_settings);
- cy.createAliasMappings(indexName, 'windows', sample_field_mappings, true);
- cy.createDetector(sample_detector);
-
- // Ingest a new document
- cy.insertDocumentToIndex(indexName, '', sample_document);
+ createDetector(
+ detectorName,
+ indexName,
+ indexSettings,
+ aliasMappings,
+ ruleSettings,
+ indexDoc,
+ 4
+ );
- // wait for detector interval to pass
+ // Wait for the detector to execute
cy.wait(DETECTOR_TRIGGER_TIMEOUT);
});
@@ -40,7 +40,11 @@ describe('Findings', () => {
cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/findings`);
// Wait for page to load
- cy.contains('Findings');
+ cy.sa_waitForPageLoad('findings', {
+ contains: 'Findings',
+ });
+
+ cy.wait(5000);
});
it('displays findings based on recently ingested data', () => {
@@ -51,19 +55,20 @@ describe('Findings', () => {
cy.contains('No items found').should('not.exist');
// Check for expected findings
- cy.contains('sample_detector');
- cy.contains('Windows');
- cy.contains('Low');
+ cy.contains('System Activity: Windows');
+ cy.contains('High');
});
it('displays finding details flyout when user clicks on View details icon', () => {
// filter table to show only sample_detector findings
- cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector');
+ cy.get(`input[placeholder="Search findings"]`).sa_ospSearch(indexName);
// Click View details icon
- cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => {
- cy.get($el).click({ force: true });
- });
+ cy.sa_getTableFirstRow('[data-test-subj="view-details-icon"]').then(
+ ($el) => {
+ cy.get($el).click({ force: true });
+ }
+ );
// Confirm flyout contents
cy.contains('Finding details');
@@ -77,10 +82,10 @@ describe('Findings', () => {
it('displays finding details flyout when user clicks on Finding ID', () => {
// filter table to show only sample_detector findings
- cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector');
+ cy.get(`input[placeholder="Search findings"]`).sa_ospSearch(indexName);
// Click findingId to trigger Finding details flyout
- cy.getTableFirstRow(
+ cy.sa_getTableFirstRow(
'[data-test-subj="finding-details-flyout-button"]'
).then(($el) => {
cy.get($el).click({ force: true });
@@ -98,7 +103,7 @@ describe('Findings', () => {
it('allows user to view details about rules that were triggered', () => {
// filter table to show only sample_detector findings
- cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector');
+ cy.get(`input[placeholder="Search findings"]`).sa_ospSearch(indexName);
// open Finding details flyout via finding id link. cy.wait essential, timeout insufficient.
cy.get(`[data-test-subj="view-details-icon"]`).eq(0).click({ force: true });
@@ -111,10 +116,9 @@ describe('Findings', () => {
// Confirm content
cy.contains('Documents');
- cy.contains('Detects plugged USB devices');
- cy.contains('Low');
+ cy.contains('USB plugged-in rule');
+ cy.contains('High');
cy.contains('Windows');
- cy.contains(indexName);
ruleTags.forEach((tag) => {
cy.contains(tag);
@@ -126,72 +130,27 @@ describe('Findings', () => {
it('opens rule details flyout when rule name inside accordion drop down is clicked', () => {
// filter table to show only sample_detector findings
- cy.get(`input[placeholder="Search findings"]`).ospSearch('sample_detector');
+ cy.get(`input[placeholder="Search findings"]`).sa_ospSearch(indexName);
// open Finding details flyout via finding id link. cy.wait essential, timeout insufficient.
- cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => {
- cy.get($el).click({ force: true });
- });
+ cy.sa_getTableFirstRow('[data-test-subj="view-details-icon"]').then(
+ ($el) => {
+ cy.get($el).click({ force: true });
+ }
+ );
// Click rule link
cy.get(
- `[data-test-subj="finding-details-flyout-USB Device Plugged-details"]`
+ `[data-test-subj="finding-details-flyout-${ruleName}-details"]`
).click({
force: true,
});
// Validate flyout appearance
- cy.get('[data-test-subj="rule_flyout_USB Device Plugged"]').within(() => {
- cy.get('[data-test-subj="rule_flyout_rule_name"]').contains(
- 'USB Device Plugged'
- );
- });
- });
-
- it('...can delete detector', () => {
- // Visit Detectors page
- cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
- cy.contains('Threat detectors');
-
- // filter table to show only sample_detector findings
- cy.get(`input[placeholder="Search threat detectors"]`).ospSearch(
- 'sample_detector'
- );
-
- // intercept detectors and rules requests
- cy.intercept(NODE_API.SEARCH_DETECTORS).as('getDetector');
- cy.intercept(`${NODE_API.RULES_SEARCH}?prePackaged=true`).as(
- 'getPrePackagedRules'
- );
- cy.intercept(`${NODE_API.RULES_SEARCH}?prePackaged=false`).as('getRules');
-
- // Click on detector to be removed
- cy.contains('sample_detector').click({ force: true });
- cy.contains('Detector details');
- cy.contains(sample_detector.name);
-
- // wait for detector details to load before continuing
- cy.wait(['@getDetector', '@getPrePackagedRules', '@getRules']).then(() => {
- // Click "Actions" button, the click "Delete"
- cy.get('button.euiButton')
- .contains('Actions')
- .click({ force: true })
- .then(() => {
- // Confirm arrival at detectors page
- cy.get('[data-test-subj="editButton"]')
- .contains('Delete')
- .click({ force: true });
-
- // Search for sample_detector, presumably deleted
- cy.get(`input[placeholder="Search threat detectors"]`).ospSearch(
- 'sample_detector'
- );
-
- // Confirm sample_detector no longer exists
- cy.contains('There are no existing detectors.');
- });
+ cy.get(`[data-test-subj="rule_flyout_${ruleName}"]`).within(() => {
+ cy.get('[data-test-subj="rule_flyout_rule_name"]').contains(ruleName);
});
});
- after(() => cy.cleanUpTests());
+ after(() => cy.sa_cleanUpTests());
});
diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js
index cfecd118f..708b1e8bc 100644
--- a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js
+++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js
@@ -3,17 +3,481 @@
* SPDX-License-Identifier: Apache-2.0
*/
-require('./detectors');
-require('./rules');
-require('./typings');
+const {
+ OPENSEARCH_DASHBOARDS_URL,
+ OPENSEARCH_DASHBOARDS,
+} = require('./constants');
+const { NODE_API } = require('./constants');
+const { BACKEND_BASE_PATH } = require('../../base_constants');
-Cypress.Commands.add('cleanUpTests', () => {
- cy.deleteAllCustomRules();
- cy.deleteAllDetectors();
- cy.deleteAllIndices();
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
+
+Cypress.Commands.add('sa_cleanUpTests', () => {
+ cy.sa_deleteAllCustomRules();
+ cy.sa_deleteAllDetectors();
+ cy.sa_deleteAllIndices();
});
-Cypress.Commands.add('getTableFirstRow', (selector) => {
+Cypress.Commands.add('sa_getTableFirstRow', (selector) => {
if (!selector) return cy.get('tbody > tr').first();
return cy.get('tbody > tr:first').find(selector);
});
+
+Cypress.Commands.add(
+ 'sa_waitForPageLoad',
+ (pathname, { timeout = 60000, contains = null }) => {
+ const fullUrl = `${OPENSEARCH_DASHBOARDS_URL}/${pathname}`;
+ Cypress.log({
+ message: `Wait for url: ${fullUrl} to be loaded.`,
+ });
+ cy.url({ timeout: timeout }).then(() => {
+ contains && cy.contains(contains).should('be.visible');
+ });
+ }
+);
+
+Cypress.Commands.add('sa_createDetector', (detectorJSON) => {
+ cy.request(
+ 'POST',
+ `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}`,
+ detectorJSON
+ );
+});
+
+Cypress.Commands.add(
+ 'sa_createAliasMappings',
+ (indexName, ruleTopic, aliasMappingsBody, partial = true) => {
+ const body = {
+ index_name: indexName,
+ rule_topic: ruleTopic,
+ partial: partial,
+ alias_mappings: aliasMappingsBody,
+ };
+ cy.request({
+ method: 'POST',
+ url: `${BACKEND_BASE_PATH}${NODE_API.MAPPINGS_BASE}`,
+ body: body,
+ });
+ }
+);
+
+Cypress.Commands.add('sa_updateDetector', (detectorId, detectorJSON) => {
+ cy.request(
+ 'PUT',
+ `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}/${detectorId}`,
+ detectorJSON
+ );
+});
+
+Cypress.Commands.add('sa_deleteDetector', (detectorName) => {
+ const body = {
+ from: 0,
+ size: 5000,
+ query: {
+ nested: {
+ path: 'detector',
+ query: {
+ bool: {
+ must: [{ match: { 'detector.name': detectorName } }],
+ },
+ },
+ },
+ },
+ };
+ cy.request({
+ method: 'POST',
+ url: `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}/_search`,
+ failOnStatusCode: false,
+ body,
+ }).then((response) => {
+ if (response.status === 200) {
+ for (let hit of response.body.hits.hits) {
+ cy.request(
+ 'DELETE',
+ `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}/${hit._id}`
+ );
+ }
+ }
+ });
+});
+
+Cypress.Commands.add('sa_deleteAllDetectors', () => {
+ cy.request({
+ method: 'DELETE',
+ url: `${BACKEND_BASE_PATH}/.opensearch-sap-detectors-config`,
+ failOnStatusCode: false,
+ }).as('deleteAllDetectors');
+ cy.get('@deleteAllDetectors').should((response) => {
+ expect(response.status).to.be.oneOf([200, 404]);
+ });
+});
+
+Cypress.Commands.add('sa_getElementByText', (locator, text) => {
+ Cypress.log({ message: `Get element by text: ${text}` });
+ return locator
+ ? cy.get(locator).filter(`:contains("${text}")`).should('be.visible')
+ : cy.contains(text).should('be.visible');
+});
+
+Cypress.Commands.add('sa_getButtonByText', (text) => {
+ Cypress.log({ message: `Get button by text: ${text}` });
+ return cy.sa_getElementByText('.euiButton', text);
+});
+
+Cypress.Commands.add('sa_getInputByPlaceholder', (placeholder) => {
+ Cypress.log({ message: `Get input element by placeholder: ${placeholder}` });
+ return cy.get(`input[placeholder="${placeholder}"]`);
+});
+
+Cypress.Commands.add('sa_getComboboxByPlaceholder', (placeholder) => {
+ Cypress.log({
+ message: `Get combobox element by placeholder: ${placeholder}`,
+ });
+ return cy
+ .sa_getElementByText('.euiComboBoxPlaceholder', placeholder)
+ .siblings('.euiComboBox__input')
+ .find('input');
+});
+
+Cypress.Commands.add('sa_getFieldByLabel', (label, type = 'input') => {
+ Cypress.log({ message: `Get field by label: ${label}` });
+ return cy
+ .sa_getElementByText('.euiFormRow__labelWrapper', label)
+ .siblings()
+ .find(type);
+});
+
+Cypress.Commands.add('sa_getTextareaByLabel', (label) => {
+ Cypress.log({ message: `Get textarea by label: ${label}` });
+ return cy.sa_getFieldByLabel(label, 'textarea');
+});
+
+Cypress.Commands.add('sa_getElementByTestSubject', (subject) => {
+ Cypress.log({ message: `Get element by test subject: ${subject}` });
+ return cy.get(`[data-test-subj="${subject}"]`);
+});
+
+Cypress.Commands.add('sa_getRadioButtonById', (id) => {
+ Cypress.log({ message: `Get radio button by id: ${id}` });
+ return cy.get(`input[id="${id}"]`);
+});
+
+Cypress.Commands.add(
+ 'sa_selectComboboxItem',
+ {
+ prevSubject: true,
+ },
+ (subject, items) => {
+ if (typeof items === 'string') {
+ items = [items];
+ }
+ Cypress.log({ message: `Select combobox items: ${items.join(' | ')}` });
+ items.map((item) => {
+ cy.wrap(subject).type(item);
+ cy.get(`[title="${item}"]`).click({ force: true });
+ });
+ }
+);
+
+Cypress.Commands.add(
+ 'sa_clearCombobox',
+ {
+ prevSubject: true,
+ },
+ (subject) => {
+ Cypress.log({ message: `Clear combobox` });
+ return cy.wrap(subject).type('{selectall}{backspace}');
+ // .parents('.euiFormRow__fieldWrapper')
+ // .find('[data-test-subj="comboBoxClearButton"]')
+ // .click({ force: true });
+ }
+);
+
+Cypress.Commands.add(
+ 'sa_containsValue',
+ {
+ prevSubject: true,
+ },
+ (subject, value) =>
+ cy.wrap(subject).parents('.euiFormRow__fieldWrapper').contains(value, {
+ matchCase: false,
+ })
+);
+
+Cypress.Commands.add(
+ 'sa_clearValue',
+ {
+ prevSubject: true,
+ },
+ (subject) => cy.wrap(subject).type('{selectall}').type('{backspace}')
+);
+
+Cypress.Commands.add(
+ 'sa_containsError',
+ {
+ prevSubject: true,
+ },
+ (subject, errorText) =>
+ cy
+ .wrap(subject)
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .contains(errorText)
+);
+
+Cypress.Commands.add(
+ 'sa_containsHelperText',
+ {
+ prevSubject: true,
+ },
+ (subject, helperText) =>
+ cy
+ .wrap(subject)
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormHelpText')
+ .contains(helperText)
+);
+
+Cypress.Commands.add(
+ 'sa_shouldNotHaveError',
+ {
+ prevSubject: true,
+ },
+ (subject) =>
+ cy
+ .wrap(subject)
+ .parents('.euiFormRow__fieldWrapper')
+ .find('.euiFormErrorText')
+ .should('not.exist')
+);
+
+Cypress.Commands.add('sa_validateDetailsItem', (label, value) => {
+ Cypress.log({
+ message: `Validate details item by label: ${label} and value: ${value}`,
+ });
+ return cy
+ .sa_getElementByText('.euiFlexItem label', label)
+ .parent()
+ .siblings()
+ .contains(value);
+});
+
+Cypress.Commands.add('sa_urlShouldContain', (path) => {
+ Cypress.log({ message: `Url should contain path: ${path}` });
+ return cy.url().should('contain', `#/${path}`);
+});
+
+Cypress.Commands.add(
+ 'sa_pressEnterKey',
+ {
+ prevSubject: true,
+ },
+ (subject) => {
+ Cypress.log({
+ message: 'Enter key pressed',
+ });
+ Cypress.automation('remote:debugger:protocol', {
+ command: 'Input.dispatchKeyEvent',
+ params: {
+ type: 'char',
+ unmodifiedText: '\r',
+ text: '\r',
+ },
+ });
+
+ return subject;
+ }
+);
+
+Cypress.Commands.add(
+ 'sa_validateTable',
+ {
+ prevSubject: true,
+ },
+ (subject, data) => {
+ Cypress.log({
+ message: 'Validate table elements',
+ });
+ return cy
+ .wrap(subject)
+ .should('be.visible')
+ .find('tbody')
+ .find('tr')
+ .then(($tr) => {
+ const length = data.length;
+ length && cy.get($tr).should('have.length', length);
+
+ cy.get($tr).within(($tr) => {
+ data.map((rowData) => {
+ rowData.forEach((tdData) => {
+ if (typeof tdData === 'string') {
+ tdData && cy.get($tr).find('td').contains(`${tdData}`);
+ } else {
+ // if rule is an object then use path
+ tdData && cy.get($tr).find('td').contains(`${tdData.path}`);
+ }
+ });
+ });
+ });
+ });
+ }
+);
+
+Cypress.Commands.add('sa_createIndex', (index, settings = {}) => {
+ cy.request('PUT', `${BACKEND_BASE_PATH}/${index}`, settings).should(
+ 'have.property',
+ 'status',
+ 200
+ );
+});
+
+Cypress.Commands.add('sa_ingestDocument', (indexId, documentJSON) => {
+ cy.request('POST', `${BACKEND_BASE_PATH}/${indexId}/_doc`, documentJSON);
+});
+
+Cypress.Commands.add(
+ 'sa_insertDocumentToIndex',
+ (indexName, documentId, documentBody) => {
+ cy.request({
+ method: 'POST',
+ url: `${BACKEND_BASE_PATH}/${indexName}/_doc/${documentId}`,
+ body: documentBody,
+ });
+ }
+);
+
+Cypress.Commands.add('sa_deleteAllIndices', () => {
+ cy.request({
+ method: 'DELETE',
+ url: `${BACKEND_BASE_PATH}/index*,sample*,opensearch_dashboards*,test*,cypress*`,
+ failOnStatusCode: false,
+ }).as('deleteAllIndices');
+ cy.get('@deleteAllIndices').should((response) => {
+ // Both statuses are a pass, 200 means deleted successfully and 404 there was no index to delete
+ expect(response.status).to.be.oneOf([200, 404]);
+ });
+});
+
+Cypress.Commands.add('sa_createRule', (ruleJSON) => {
+ return cy.request({
+ method: 'POST',
+ url: `${OPENSEARCH_DASHBOARDS}${NODE_API.RULES_BASE}?category=${ruleJSON.category}`,
+ body: JSON.stringify(ruleJSON),
+ headers: {
+ 'osd-xsrf': true,
+ },
+ });
+});
+
+Cypress.Commands.add('sa_updateRule', (ruleId, ruleJSON) => {
+ cy.request(
+ 'PUT',
+ `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/${ruleId}`,
+ ruleJSON
+ );
+});
+
+Cypress.Commands.add('sa_deleteRule', (ruleName) => {
+ const body = {
+ from: 0,
+ size: 5000,
+ query: {
+ nested: {
+ path: 'rule',
+ query: {
+ bool: {
+ must: [{ match: { 'rule.title': 'Cypress test rule' } }],
+ },
+ },
+ },
+ },
+ };
+ cy.request({
+ method: 'POST',
+ url: `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=false`,
+ failOnStatusCode: false,
+ body,
+ }).then((response) => {
+ if (response.status === 200) {
+ for (let hit of response.body.hits.hits) {
+ if (hit._source.title === ruleName)
+ cy.request(
+ 'DELETE',
+ `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/${hit._id}?forced=true`
+ );
+ }
+ }
+ });
+});
+
+Cypress.Commands.add('sa_deleteAllCustomRules', () => {
+ const url = `${BACKEND_BASE_PATH}/.opensearch-sap-custom-rules-config`;
+ cy.request({
+ method: 'DELETE',
+ url: url,
+ failOnStatusCode: false,
+ body: { query: { match_all: {} } },
+ }).as('deleteAllCustomRules');
+ cy.get('@deleteAllCustomRules').should((response) => {
+ expect(response.status).to.be.oneOf([200, 404]);
+ });
+});
+
+Cypress.Commands.add(
+ 'sa_ospSearch',
+ {
+ prevSubject: true,
+ },
+ (subject, text) => {
+ return cy.get(subject).clear().sa_ospType(text).realPress('Enter');
+ }
+);
+
+Cypress.Commands.add(
+ 'sa_ospClear',
+ {
+ prevSubject: true,
+ },
+ (subject) => {
+ return cy
+ .get(subject)
+ .wait(100)
+ .type('{selectall}{backspace}')
+ .clear({ force: true })
+ .invoke('val', '');
+ }
+);
+
+Cypress.Commands.add(
+ 'sa_ospType',
+ {
+ prevSubject: true,
+ },
+ (subject, text) => {
+ return cy.get(subject).wait(10).focus().realType(text);
+ }
+);
diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js
index 1b6c46edf..79c637364 100644
--- a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js
+++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js
@@ -3,27 +3,31 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { BASE_PATH } from '../../base_constants';
-
-export const PLUGIN_NAME = 'opensearch_security_analytics_dashboards';
-export const BASE_API_PATH = '/_plugins/_security_analytics';
+import sample_detector from '../../../fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/detector/create_usb_detector_data.json';
export const TWENTY_SECONDS_TIMEOUT = { timeout: 20000 };
+
export const DETECTOR_TRIGGER_TIMEOUT = 65000;
export const FEATURE_SYSTEM_INDICES = {
DETECTORS_INDEX: '.opensearch-detectors-config',
DETECTOR_QUERIES_INDEX: '.opensearch-sap-windows-detectors-queries',
PRE_PACKAGED_RULES_INDEX: '.opensearch-pre-packaged-rules-config',
- CUSTOM_RULES_INDEX: '.opensearch-sap-custom-rules-config',
+ CUSTOM_RULES_INDEX: '.opensearch-custom-rules-config',
WINDOWS_ALERTS_INDEX: '.opensearch-sap-windows-alerts*',
WINDOWS_FINDINGS_INDEX: '.opensearch-sap-windows-findings*',
};
+export const PLUGIN_NAME = 'opensearch_security_analytics_dashboards';
+
+export const BASE_API_PATH = '/_plugins/_security_analytics';
+
export const NODE_API = {
DETECTORS_BASE: `${BASE_API_PATH}/detectors`,
+ CORRELATION_BASE: `${BASE_API_PATH}/correlation/rules`,
SEARCH_DETECTORS: `${BASE_API_PATH}/detectors/_search`,
INDICES_BASE: `${BASE_API_PATH}/indices`,
+ FINDINGS_BASE: `${BASE_API_PATH}/findings`,
GET_FINDINGS: `${BASE_API_PATH}/findings/_search`,
DOCUMENT_IDS_QUERY: `${BASE_API_PATH}/document_ids_query`,
TIME_RANGE_QUERY: `${BASE_API_PATH}/time_range_query`,
@@ -31,11 +35,94 @@ export const NODE_API = {
MAPPINGS_VIEW: `${BASE_API_PATH}/mappings/view`,
GET_ALERTS: `${BASE_API_PATH}/alerts`,
RULES_BASE: `${BASE_API_PATH}/rules`,
- RULES_SEARCH: `${BASE_API_PATH}/rules/_search`,
CHANNELS: `${BASE_API_PATH}/_notifications/channels`,
PLUGINS: `${BASE_API_PATH}/_notifications/plugins`,
ACKNOWLEDGE_ALERTS: `${BASE_API_PATH}/detectors/{detector_id}/_acknowledge/alerts`,
+ UPDATE_ALIASES: `${BASE_API_PATH}/update_aliases`,
+ CORRELATIONS: `${BASE_API_PATH}/correlations`,
+ LOGTYPE_BASE: `${BASE_API_PATH}/logtype`,
INDEX_TEMPLATE_BASE: '/_index_template',
};
-export const OPENSEARCH_DASHBOARDS_URL = `${BASE_PATH}/app/${PLUGIN_NAME}#`;
+export const { baseUrl: OPENSEARCH_DASHBOARDS } = Cypress.config();
+export const OPENSEARCH_DASHBOARDS_URL = `${OPENSEARCH_DASHBOARDS}/app/${PLUGIN_NAME}#`;
+
+export const createDetector = (
+ detectorName,
+ indexName,
+ indexSettings,
+ indexMappings,
+ ruleSettings,
+ indexDoc,
+ indexDocsCount = 1
+) => {
+ Cypress.log({
+ message: `Create new detector ${detectorName}`,
+ });
+ const detectorConfigAlertCondition = `${detectorName} alert condition`;
+ const detectorConfig = {
+ ...sample_detector,
+ name: detectorName,
+ inputs: [
+ {
+ detector_input: {
+ ...sample_detector.inputs[0].detector_input,
+ description: `Description for ${detectorName}`,
+ indices: [indexName],
+ },
+ },
+ ],
+ triggers: [
+ {
+ ...sample_detector.triggers[0],
+ name: detectorConfigAlertCondition,
+ },
+ ],
+ };
+
+ cy.sa_cleanUpTests()
+ // Create test index
+ .then(() => cy.sa_createIndex(indexName, indexSettings))
+
+ // Create field mappings
+ .then(() =>
+ cy.sa_createAliasMappings(
+ indexName,
+ detectorConfig.detector_type,
+ indexMappings,
+ true
+ )
+ )
+ // Create rule
+ .then(() => {
+ cy.sa_createRule(ruleSettings)
+ .then((response) => {
+ detectorConfig.inputs[0].detector_input.custom_rules[0].id =
+ response.body.response._id;
+ detectorConfig.triggers[0].ids.push(response.body.response._id);
+ })
+ // create the detector
+ .then(() => cy.sa_createDetector(detectorConfig));
+ })
+ .then(() => {
+ // Go to the detectors table page
+ cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
+
+ // Filter table to only show the test detector
+ cy.get(`input[type="search"]`).type(`${detectorConfig.name}{enter}`);
+
+ // Confirm detector was created
+ cy.get('tbody > tr').should(($tr) => {
+ expect($tr, 'detector name').to.contain(detectorConfig.name);
+ });
+ });
+
+ // Wait for the first run to execute before ingesting data
+ cy.wait(65000);
+ // Ingest documents to the test index
+ for (let i = 0; i < indexDocsCount; i++) {
+ cy.sa_insertDocumentToIndex(indexName, '', indexDoc);
+ }
+
+ return detectorConfig;
+};
diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/detectors.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/detectors.js
deleted file mode 100644
index 10207f7ae..000000000
--- a/cypress/utils/plugins/security-analytics-dashboards-plugin/detectors.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright OpenSearch Contributors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-const { BACKEND_BASE_PATH } = require('../../base_constants');
-const { NODE_API } = require('./constants');
-
-Cypress.Commands.add('createDetector', (detectorJSON) => {
- cy.request(
- 'POST',
- `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}`,
- detectorJSON
- );
-});
-
-Cypress.Commands.add(
- 'createAliasMappings',
- (indexName, ruleTopic, aliasMappingsBody, partial = true) => {
- const body = {
- index_name: indexName,
- rule_topic: ruleTopic,
- partial: partial,
- alias_mappings: aliasMappingsBody,
- };
- cy.request({
- method: 'POST',
- url: `${BACKEND_BASE_PATH}${NODE_API.MAPPINGS_BASE}`,
- body: body,
- });
- }
-);
-
-Cypress.Commands.add('updateDetector', (detectorId, detectorJSON) => {
- cy.request(
- 'PUT',
- `${BACKEND_BASE_PATH}/${NODE_API.DETECTORS_BASE}/${detectorId}`,
- detectorJSON
- );
-});
-
-Cypress.Commands.add('deleteSAPDetector', (detectorName) => {
- const body = {
- from: 0,
- size: 5000,
- query: {
- nested: {
- path: 'detector',
- query: {
- bool: {
- must: [{ match: { 'detector.name': detectorName } }],
- },
- },
- },
- },
- };
- cy.request({
- method: 'POST',
- url: `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}/_search`,
- failOnStatusCode: false,
- body,
- }).then((response) => {
- if (response.status === 200) {
- for (let hit of response.body.hits.hits) {
- cy.request(
- 'DELETE',
- `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}/${hit._id}`
- );
- }
- }
- });
-});
-
-Cypress.Commands.add('deleteAllDetectors', () => {
- cy.request({
- method: 'DELETE',
- url: `${BACKEND_BASE_PATH}/.opensearch-sap-detectors-config`,
- failOnStatusCode: false,
- });
-});
diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts
new file mode 100644
index 000000000..5e0eb0119
--- /dev/null
+++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts
@@ -0,0 +1,283 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// eslint-disable-next-line
+///
+
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * Returns element by its text
+ * @example
+ * cy.sa_getElementByText('.euiTitle', 'Some title')
+ */
+ sa_getElementByText(locator: string, text: string): Chainable;
+
+ /**
+ * Returns button by its text
+ * @example
+ * cy.sa_getButtonByText('Button text')
+ */
+ sa_getButtonByText(text: string): Chainable;
+
+ /**
+ * Returns input by its placeholder
+ * @example
+ * cy.sa_getInputByPlaceholder('Search rules...')
+ */
+ sa_getInputByPlaceholder(placeholder: string): Chainable;
+
+ /**
+ * Returns combobox input by its placeholder
+ * @example
+ * cy.sa_getComboboxByPlaceholder('Select data input...')
+ */
+ sa_getComboboxByPlaceholder(placeholder: string): Chainable;
+
+ /**
+ * Returns field input by label
+ * @example
+ * cy.sa_getFieldByLabel('Detector name')
+ */
+ sa_getFieldByLabel(label: string, type?: string): Chainable;
+
+ /**
+ * Returns textarea by label
+ * @example
+ * cy.sa_getTextareaByLabel('Detector description')
+ */
+ sa_getTextareaByLabel(label: string): Chainable;
+
+ /**
+ * Returns element by data-test-subj attribute value
+ * @example
+ * cy.sa_getElementByTestSubject('alerts-input-element')
+ */
+ sa_getElementByTestSubject(subject: string): Chainable;
+
+ /**
+ * Returns radio by id
+ * @example
+ * cy.sa_getRadioButtonById('radioId')
+ */
+ sa_getRadioButtonById(id: string): Chainable;
+
+ /**
+ * Selects combobox item(s)
+ * @example
+ * cy.get('combo).sa_selectComboboxItem('some item value')
+ */
+ sa_selectComboboxItem(items: string | string[]): Chainable;
+
+ /**
+ * Clears combobox value(s)
+ * @example
+ * cy.get('combo).sa_clearCombobox()
+ */
+ sa_clearCombobox(): Chainable;
+
+ /**
+ * Triggers enter key event on the focused element
+ * @example
+ * cy.sa_pressEnterKey()
+ */
+ sa_pressEnterKey(): Chainable;
+
+ /**
+ * Triggers backspace key event on the focused element
+ * @example
+ * cy.sa_pressBackspaceKey()
+ */
+ sa_pressBackspaceKey(numberOfPresses?: number): Chainable;
+
+ /**
+ * Validates details panel item
+ * @example
+ * cy.sa_validateDetailsItem('Data source', '.index-name')
+ */
+ sa_validateDetailsItem(label: string, value: string): Chainable;
+
+ /**
+ * Should clear a field value (use with text and textarea fields)
+ * @example
+ * cy.sa_getFieldByLabel('Rule name').sa_clearValue()
+ */
+ sa_clearValue(): Chainable;
+
+ /**
+ * Validates that field contains value
+ * Should be used with combobox or other fields that don't print its value in inputs
+ * @example
+ * cy.sa_getFieldByLabel('Rule name').sa_containsValue('Name')
+ */
+ sa_containsValue(value: string): Chainable;
+
+ /**
+ * Validates that field has error text
+ * @example
+ * cy.sa_getFieldByLabel('Rule name').sa_containsError('This fields is invalid')
+ */
+ sa_containsError(errorText: string): Chainable;
+
+ /**
+ * Validates that field has helper text
+ * @example
+ * cy.sa_getFieldByLabel('Rule name').sa_containsHelperText('Use this field for...')
+ */
+ sa_containsHelperText(helperText: string): Chainable;
+
+ /**
+ * Should not have error text
+ * @example
+ * cy.sa_getFieldByLabel('Rule name').sa_shouldNotHaveError()
+ */
+ sa_shouldNotHaveError(): Chainable;
+
+ /**
+ * Validates url path
+ * @example
+ * cy.sa_urlShouldContain('/detector-details')
+ */
+ sa_urlShouldContain(path: string): Chainable;
+
+ /**
+ * Validates table items
+ * @example
+ * cy.sa_validateTable('/detector-details')
+ */
+ sa_validateTable(data: { [key: string]: string }[]): Chainable;
+
+ /**
+ * Removes custom indices, detectors and rules
+ * @example
+ * cy.sa_cleanUpTests()
+ */
+ sa_cleanUpTests(): Chainable;
+
+ /**
+ * Returns table first row
+ * Finds elements deeper in a row with selector
+ * @param {string} selector
+ * @example
+ * cy.sa_getTableFirstRow()
+ * cy.sa_getTableFirstRow('td')
+ */
+ sa_getTableFirstRow(selector: string): Chainable;
+
+ /**
+ * Waits for page to be loaded
+ * @param {string} pathname
+ * @param {any} opts
+ * @example
+ * cy.sa_waitForPageLoad('detectors')
+ * cy.sa_waitForPageLoad('detectors', {
+ * timeout: 20000,
+ * contains: 'text to verify'
+ * })
+ */
+ sa_waitForPageLoad(pathname: string, opts?: any): Chainable;
+
+ /**
+ * Returns table first row
+ * Can find elements deeper in a row with selector
+ * @param {string} text
+ * @example
+ * cy.get('selector').sa_ospSearch('Txt to write into input')
+ */
+ sa_ospSearch(text: string): Chainable;
+
+ /**
+ * Clears input text
+ * @example
+ * cy.get('selector').sa_ospClear()
+ */
+ sa_ospClear(): Chainable;
+
+ /**
+ * Returns table first row
+ * Can find elements deeper in a row with selector
+ * @param {string} text
+ * @example
+ * cy.get('selector').sa_ospType('Txt to write into input')
+ */
+ sa_ospType(text: string): Chainable;
+
+ /**
+ * Creates index with optional settings
+ * @example
+ * cy.sa_createIndex("some_index", settingObj)
+ */
+ sa_createIndex(index: string, settings?: object): Chainable;
+
+ /**
+ * Creates an index template.
+ * @example
+ * cy.sa_createIndexTemplate("some_index_template", { "index_patterns": "abc", "properties": { ... } })
+ */
+ sa_createIndexTemplate(name: string, template: object): Chainable;
+
+ /**
+ /**
+ * Deletes all indices in cluster
+ * @example
+ * cy.sa_deleteAllIndices()
+ */
+ sa_deleteAllIndices(): Chainable;
+
+ /**
+ * Deletes all custom rules in cluster
+ * @example
+ * cy.sa_deleteAllCustomRules()
+ */
+ sa_deleteAllCustomRules(): Chainable;
+
+ /**
+ * Deletes all detectors in cluster
+ * @example
+ * cy.sa_deleteAllDetectors()
+ */
+ sa_deleteAllDetectors(): Chainable;
+
+ /**
+ * Creates a detector
+ * @example
+ * cy.sa_createPolicy({ "detector_type": ... })
+ */
+ sa_createDetector(detectorJSON: object): Chainable;
+
+ /**
+ * Creates a fields mapping aliases for detector
+ * @example
+ * cy.sa_createAliasMappings('indexName', 'windows', {...}, true)
+ */
+ sa_createAliasMappings(
+ indexName: string,
+ ruleTopic: string,
+ aliasMappingsBody: object,
+ partial: boolean
+ ): Chainable;
+
+ /**
+ * Creates a custom rule
+ * @example
+ * cy.sa_createRule({})
+ */
+ sa_createRule(ruleJSON: object): Chainable;
+
+ /**
+ * Updates settings for index
+ * @example
+ * cy.sa_updateIndexSettings("some_index", settings)
+ */
+ sa_updateDetector(detectorId: string, detectorJSON: object): Chainable;
+
+ /**
+ * Deletes detector by its name
+ * @example
+ * cy.sa_deleteDetector("Cypress detector name")
+ */
+ sa_deleteDetector(name: string): Chainable;
+ }
+}
diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/rules.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/rules.js
deleted file mode 100644
index 53313fa94..000000000
--- a/cypress/utils/plugins/security-analytics-dashboards-plugin/rules.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright OpenSearch Contributors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-const { BASE_PATH } = require('../../base_constants');
-const { FEATURE_SYSTEM_INDICES, NODE_API } = require('./constants');
-
-Cypress.Commands.add('createRule', (ruleJSON) => {
- cy.request({
- method: 'POST',
- url: `${BASE_PATH}${NODE_API.RULES_BASE}?category=${ruleJSON.category}`,
- body: JSON.stringify(ruleJSON),
- headers: {
- 'osd-xsrf': false,
- },
- });
-});
-
-Cypress.Commands.add('updateRule', (ruleId, ruleJSON) => {
- cy.request('PUT', `${BASE_PATH}/${NODE_API.RULES_BASE}/${ruleId}`, ruleJSON);
-});
-
-Cypress.Commands.add('deleteRule', (ruleName) => {
- const body = {
- from: 0,
- size: 5000,
- query: {
- nested: {
- path: 'rule',
- query: {
- bool: {
- must: [{ match: { 'rule.title': 'Cypress test rule' } }],
- },
- },
- },
- },
- };
- cy.request({
- method: 'POST',
- url: `${BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=false`,
- failOnStatusCode: false,
- body,
- }).then((response) => {
- if (response.status === 200) {
- for (let hit of response.body.hits.hits) {
- if (hit._source.title === ruleName)
- cy.request(
- 'DELETE',
- `${BASE_PATH}${NODE_API.RULES_BASE}/${hit._id}?forced=true`
- );
- }
- }
- });
-});
-
-Cypress.Commands.add('deleteAllCustomRules', () => {
- const url = `${BASE_PATH}/${FEATURE_SYSTEM_INDICES.CUSTOM_RULES_INDEX}`;
- cy.request({
- method: 'DELETE',
- url: url,
- failOnStatusCode: false,
- body: { query: { match_all: {} } },
- });
-});
diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/typings.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/typings.js
deleted file mode 100644
index c354bf6f7..000000000
--- a/cypress/utils/plugins/security-analytics-dashboards-plugin/typings.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright OpenSearch Contributors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-Cypress.Commands.add(
- 'ospSearch',
- {
- prevSubject: true,
- },
- (subject, text) => {
- return cy.get(subject).clear().ospType(text);
- }
-);
-
-Cypress.Commands.add(
- 'ospClear',
- {
- prevSubject: true,
- },
- (subject) => {
- return cy
- .get(subject)
- .wait(100)
- .type('{selectall}{backspace}')
- .clear({ force: true })
- .invoke('val', '');
- }
-);
-
-Cypress.Commands.add(
- 'ospType',
- {
- prevSubject: true,
- },
- (subject, text) => {
- return cy.get(subject).wait(10).focus().realType(text).realPress('Enter');
- }
-);