From 4845011e37a31dd058c868be1cf077703f521aa8 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 6 Sep 2023 14:46:18 -0700 Subject: [PATCH 1/9] updated tests Signed-off-by: Amardeepsingh Siglani --- .../detector/create_dns_detector_data.json | 10 +- .../create_dns_detector_mappings_data.json | 6 +- .../detector/create_usb_detector_data.json | 6 +- .../create_usb_detector_mappings_data.json | 24 +- .../index/add_dns_index_data.json | 6 +- .../index/add_windows_index_data.json | 38 +- .../index/create_dns_settings.json | 6 +- .../index/create_windows_settings.json | 16 +- ... create_dns_rule_with_name_selection.json} | 6 +- .../create_dns_rule_with_type_selection.json | 26 + .../rule/create_network_rule.json | 4 +- .../rule/create_windows_usb_rule.json | 4 +- .../rule/sample_dns_field_mappings.json | 5 + .../sample_alias_mappings.json | 12 +- .../sample_detector.json | 14 +- .../sample_dns_index_settings.json | 21 + .../sample_document.json | 38 +- .../sample_field_mappings.json | 24 +- .../sample_index_settings.json | 33 - .../sample_windows_index_settings.json | 18 + .../1_detectors.spec.js | 800 +++++++++--------- .../1_detectors_new.spec.js | 601 +++++++++++++ .../2_rules.spec.js | 678 +++++++++++---- .../3_alerts.spec.js | 207 ++--- .../4_findings.spec.js | 117 +-- .../commands.js | 537 +++++++++++- .../constants.js | 99 ++- .../detectors.js | 80 -- .../index.d.ts | 283 +++++++ .../index.js | 27 + .../rules.js | 65 -- .../typings.js | 39 - 32 files changed, 2674 insertions(+), 1176 deletions(-) rename cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/{create_dns_rule.json => create_dns_rule_with_name_selection.json} (72%) create mode 100644 cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/create_dns_rule_with_type_selection.json create mode 100644 cypress/fixtures/plugins/security-analytics-dashboards-plugin/integration_tests/rule/sample_dns_field_mappings.json create mode 100644 cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_dns_index_settings.json delete mode 100644 cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_index_settings.json create mode 100644 cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json create mode 100644 cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js delete mode 100644 cypress/utils/plugins/security-analytics-dashboards-plugin/detectors.js create mode 100644 cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts create mode 100644 cypress/utils/plugins/security-analytics-dashboards-plugin/index.js delete mode 100644 cypress/utils/plugins/security-analytics-dashboards-plugin/rules.js delete mode 100644 cypress/utils/plugins/security-analytics-dashboards-plugin/typings.js 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..0cad430bc 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" + "path": "winlog.event_id" }, - "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..f8b8b4e2e 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" + "winlog.event_id": "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..480f63ba1 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": { + "winlog.event_id": { "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..fb14944c6 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 @@ -16,8 +16,8 @@ } ], "log_source": "", - "detection": "selection:\n EventID:\n - 2003\n - 2100\n - 2102\ncondition: selection", - "level": "low", + "detection": "selection:\n winlog-event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", + "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..e0a1a5f88 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": "winlog.event_id" } } } 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..521d2f677 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" + "winlog.event_id": 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..ff4eb1830 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,27 +1,7 @@ { "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": { - "path": "EventID", + "winlog-event_id": { + "path": "winlog.event_id", "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..480f63ba1 --- /dev/null +++ b/cypress/fixtures/plugins/security-analytics-dashboards-plugin/sample_windows_index_settings.json @@ -0,0 +1,18 @@ +{ + "mappings": { + "properties": { + "winlog.event_id": { + "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..869a6f1e9 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,415 +3,415 @@ * 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'; - -const testMappings = { - properties: { - 'dns-question-name': { - type: 'alias', - path: 'DnsQuestionName', - }, - }, -}; - -const cypressDNSRule = dns_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 }); - - // Check to ensure process started - cy.contains('Define detector'); - - // Enter a name for the detector in the appropriate input - cy.get(`input[placeholder="Enter a name for the detector."]`) - .focus() - .realType(detectorName); - - // 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, - }); - - cy.contains('table tr', 'DNS', { - timeout: 120000, - }); - }); - - // Check that correct page now showing - cy.contains('Configure field mapping'); - - if (!expectFailure) { - // Select appropriate names to map fields to - for (let field_name in testMappings.properties) { - const mappedTo = testMappings.properties[field_name].path; - - cy.contains('tr', field_name).within(() => { - cy.get(`[data-test-subj="detector-field-mappings-select"]`) - .click() - .type(mappedTo); - }); - } - } - - // 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'); - - // Select applicable severity levels - cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); - cy.contains('1 (Highest)').click({ force: true }); - - // Continue to next page - cy.contains('Next').click({ force: true }); - - // Confirm page is reached - cy.contains('Review and create'); - - // Confirm field mappings registered - cy.contains('Field mapping'); - - if (!expectFailure) { - for (let field in testMappings.properties) { - const mappedTo = testMappings.properties[field].path; - - cy.contains(field); - cy.contains(mappedTo); - } - } - - // Confirm entries user has made - cy.contains('Detector details'); - cy.contains(detectorName); - cy.contains('dns'); - cy.contains('test_trigger'); - - // Create the detector - cy.get('button').contains('Create').click({ force: true }); - cy.contains(detectorName); - - cy.contains('Attempting to create the detector.'); - - // Confirm detector active - cy.contains(detectorName); - cy.contains('Active'); - - if (!expectFailure) { - cy.contains('Actions'); - } - - 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'; - - before(() => { - cy.cleanUpTests(); - - cy.createIndex(cypressIndexWindows, null, sample_index_settings); - - // Create test index - cy.createIndex(cypressIndexDns, null, sample_index_settings).then(() => - cy - .request({ - method: 'POST', - url: `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=true`, - headers: { - 'osd-xsrf': true, - }, - body: { - from: 0, - size: 5000, - query: { - nested: { - path: 'rule', - query: { - bool: { must: [{ match: { 'rule.category': 'dns' } }] }, - }, - }, - }, - }, - }) - .should('have.property', 'status', 200) - ); - - cy.createRule(dns_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'); - - // Check that correct page is showing - cy.contains('Threat detectors'); - }); - - 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.'); - }); - - it('...basic details can be edited', () => { - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.contains('Detector details'); - cy.contains(detectorName); - - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ - force: true, - }); - - // 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, - }); - - // Confirm taken to detector details page - cy.contains(detectorName); - - // Verify edits are applied - cy.contains('test detector edited'); - cy.contains('Every 10 hours'); - cy.contains('Edited description'); - cy.contains(cypressIndexWindows); - }); - - it('...rules can be edited', () => { - // Ensure start on main detectors page - cy.contains('Threat detectors'); - - // Click on detector name - cy.contains(detectorName).click({ force: true }); - cy.contains('Detector details'); - cy.contains(detectorName); - - // Confirm number of rules before edit - cy.contains('Active rules (13)'); - - // Click "Edit" button in Detector rules panel - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - - // Confirm arrival on "Edit detector rules" page - cy.contains('Edit detector rules'); - - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // 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 }); - }); - - // Save changes - cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ - force: true, - }); - - // Confirm 1 rule has been removed from detector - cy.contains('Active rules (12)'); - - // Click "Edit" button in Detector rules panel - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - - // Confirm arrival on "Edit detector rules" page - cy.contains('Edit detector rules'); - - // Search for specific rule - cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - - // Toggle single search result to checked - cy.contains('table tr', cypressDNSRule).within(() => { - cy.wait(2000); - cy.get('button').eq(1).click({ force: true }); - }); - - // Save changes - cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ - force: true, - }); - cy.contains(detectorName); - - // Confirm 1 rule has been added to detector - cy.contains('Active rules (13)'); - }); - - 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); - - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ - force: true, - }); - - // Confirm arrival at "Edit detector details" page - cy.contains('Edit detector details'); - - cy.get('.reviewFieldMappings').should('not.exist'); - - // 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'); - }); +// 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'; + +// const testMappings = { +// properties: { +// 'dns-question-name': { +// type: 'alias', +// path: 'DnsQuestionName', +// }, +// }, +// }; + +// const cypressDNSRule = dns_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 }); + +// // Check to ensure process started +// cy.contains('Define detector'); + +// // Enter a name for the detector in the appropriate input +// cy.get(`input[placeholder="Enter a name for the detector."]`) +// .focus() +// .realType(detectorName); + +// // 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, +// }); + +// cy.contains('table tr', 'DNS', { +// timeout: 120000, +// }); +// }); + +// // Check that correct page now showing +// cy.contains('Configure field mapping'); + +// if (!expectFailure) { +// // Select appropriate names to map fields to +// for (let field_name in testMappings.properties) { +// const mappedTo = testMappings.properties[field_name].path; + +// cy.contains('tr', field_name).within(() => { +// cy.get(`[data-test-subj="detector-field-mappings-select"]`) +// .click() +// .type(mappedTo); +// }); +// } +// } + +// // 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'); + +// // Select applicable severity levels +// cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); +// cy.contains('1 (Highest)').click({ force: true }); + +// // Continue to next page +// cy.contains('Next').click({ force: true }); + +// // Confirm page is reached +// cy.contains('Review and create'); + +// // Confirm field mappings registered +// cy.contains('Field mapping'); + +// if (!expectFailure) { +// for (let field in testMappings.properties) { +// const mappedTo = testMappings.properties[field].path; + +// cy.contains(field); +// cy.contains(mappedTo); +// } +// } + +// // Confirm entries user has made +// cy.contains('Detector details'); +// cy.contains(detectorName); +// cy.contains('dns'); +// cy.contains('test_trigger'); + +// // Create the detector +// cy.get('button').contains('Create').click({ force: true }); +// cy.contains(detectorName); + +// cy.contains('Attempting to create the detector.'); + +// // Confirm detector active +// cy.contains(detectorName); +// cy.contains('Active'); + +// if (!expectFailure) { +// cy.contains('Actions'); +// } + +// 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'; + +// before(() => { +// cy.cleanUpTests(); + +// cy.createIndex(cypressIndexWindows, null, sample_index_settings); + +// // Create test index +// cy.createIndex(cypressIndexDns, null, sample_index_settings).then(() => +// cy +// .request({ +// method: 'POST', +// url: `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=true`, +// headers: { +// 'osd-xsrf': true, +// }, +// body: { +// from: 0, +// size: 5000, +// query: { +// nested: { +// path: 'rule', +// query: { +// bool: { must: [{ match: { 'rule.category': 'dns' } }] }, +// }, +// }, +// }, +// }, +// }) +// .should('have.property', 'status', 200) +// ); + +// cy.createRule(dns_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'); + +// // Check that correct page is showing +// cy.contains('Threat detectors'); +// }); + +// 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.'); +// }); + +// it('...basic details can be edited', () => { +// // Click on detector name +// cy.contains(detectorName).click({ force: true }); +// cy.contains('Detector details'); +// cy.contains(detectorName); + +// // Click "Edit" button in detector details +// cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ +// force: true, +// }); + +// // 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, +// }); + +// // Confirm taken to detector details page +// cy.contains(detectorName); + +// // Verify edits are applied +// cy.contains('test detector edited'); +// cy.contains('Every 10 hours'); +// cy.contains('Edited description'); +// cy.contains(cypressIndexWindows); +// }); + +// it('...rules can be edited', () => { +// // Ensure start on main detectors page +// cy.contains('Threat detectors'); + +// // Click on detector name +// cy.contains(detectorName).click({ force: true }); +// cy.contains('Detector details'); +// cy.contains(detectorName); + +// // Confirm number of rules before edit +// cy.contains('Active rules (13)'); + +// // Click "Edit" button in Detector rules panel +// cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); + +// // Confirm arrival on "Edit detector rules" page +// cy.contains('Edit detector rules'); + +// // Search for specific rule +// cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); + +// // 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 }); +// }); + +// // Save changes +// cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ +// force: true, +// }); + +// // Confirm 1 rule has been removed from detector +// cy.contains('Active rules (12)'); + +// // Click "Edit" button in Detector rules panel +// cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); + +// // Confirm arrival on "Edit detector rules" page +// cy.contains('Edit detector rules'); + +// // Search for specific rule +// cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); + +// // Toggle single search result to checked +// cy.contains('table tr', cypressDNSRule).within(() => { +// cy.wait(2000); +// cy.get('button').eq(1).click({ force: true }); +// }); + +// // Save changes +// cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ +// force: true, +// }); +// cy.contains(detectorName); + +// // Confirm 1 rule has been added to detector +// cy.contains('Active rules (13)'); +// }); + +// 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); + +// // Click "Edit" button in detector details +// cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ +// force: true, +// }); + +// // Confirm arrival at "Edit detector details" page +// cy.contains('Edit detector details'); + +// cy.get('.reviewFieldMappings').should('not.exist'); + +// // 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'); +// }); - 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); +// 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); - // Click "Edit" button in detector details - cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); +// // Click "Edit" button in detector details +// cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - // Confirm arrival at "Edit detector details" page - cy.contains('Edit detector rules'); +// // Confirm arrival at "Edit detector details" page +// cy.contains('Edit detector rules'); - cy.get('.reviewFieldMappings').should('not.exist'); +// cy.get('.reviewFieldMappings').should('not.exist'); - cy.intercept(NODE_API.MAPPINGS_VIEW).as('getMappingsView'); +// cy.intercept(NODE_API.MAPPINGS_VIEW).as('getMappingsView'); - cy.get('table th').within(() => { - cy.get('button').first().click({ force: true }); - }); +// cy.get('table th').within(() => { +// cy.get('button').first().click({ force: true }); +// }); - cy.get('.reviewFieldMappings').should('be.visible'); - }); +// cy.get('.reviewFieldMappings').should('be.visible'); +// }); - it('...can be deleted', () => { - // Click on detector to be removed - cy.contains('test detector edited').click({ force: true }); +// it('...can be deleted', () => { +// // Click on detector to be removed +// cy.contains('test detector edited').click({ force: true }); - // Confirm page - cy.contains('Detector details'); +// // Confirm page +// cy.contains('Detector details'); - // Click "Actions" button, the click "Delete" - cy.get('button').contains('Actions').click({ force: true }); - cy.get('button').contains('Delete').click({ force: true }); +// // Click "Actions" button, the click "Delete" +// cy.get('button').contains('Actions').click({ force: true }); +// cy.get('button').contains('Delete').click({ force: true }); - // Confirm detector is deleted - cy.contains('There are no existing detectors'); - }); +// // Confirm detector is deleted +// cy.contains('There are no existing detectors'); +// }); - after(() => cy.cleanUpTests()); -}); +// after(() => cy.cleanUpTests()); +// }); diff --git a/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js b/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js new file mode 100644 index 000000000..b47b98c7b --- /dev/null +++ b/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js @@ -0,0 +1,601 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +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'; + +const cypressIndexDns = 'cypress-index-dns'; +const cypressIndexWindows = 'cypress-index-windows'; +const detectorName = 'test detector'; +const cypressLogTypeDns = 'dns'; + +const cypressDNSRule = dns_name_rule_data.title; + +const getNameField = () => + cy.getInputByPlaceholder('Enter a name for the detector.'); + +const getNextButton = () => cy.getButtonByText('Next'); + +const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); + +const validateAlertPanel = (alertName) => + cy + .getElementByText('.euiTitle', 'Alert triggers') + .parentsUntil('.euiPanel') + .siblings() + .eq(2) + .within(() => cy.getElementByText('button', alertName)); + +const dataSourceLabel = 'Select or input source indexes or index patterns'; + +const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel); + +const logTypeLabel = 'Select a log type you would like to detect'; + +const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel); + +const openDetectorDetails = (detectorName) => { + cy.getInputByPlaceholder('Search threat detectors') + .type(`${detectorName}`) + .pressEnterKey(); + cy.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; +}; + +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); + } + }); + }); +}; + +const editDetectorDetails = (detectorName, panelTitle) => { + cy.urlShouldContain('detector-details').then(() => { + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiPanel .euiTitle', panelTitle); + cy.getElementByText('.euiPanel .euiTitle', panelTitle) + .parent() + .siblings() + .within(() => cy.get('button').contains('Edit').click()); + }); +}; + +const validateAutomaticFieldMappingsPanel = (mappings) => + cy.get('.editFieldMappings').within(() => { + cy.get('.euiAccordion__triggerWrapper button').then(($btn) => { + cy.get($btn).contains(`Automatically mapped fields (${mappings.length})`); + + // 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.getElementByTestSubject('auto-mapped-fields-table') + .find('.euiBasicTable') + .validateTable(mappings); + }); + } + }); + }); + +const validatePendingFieldMappingsPanel = (mappings) => { + cy.get('.editFieldMappings').within(() => { + // Pending field mappings + cy.getElementByText('.euiTitle', 'Pending field mappings') + .parents('.euiPanel') + .within(() => { + cy.getElementByTestSubject('pending-mapped-fields-table') + .find('.euiBasicTable') + .validateTable(mappings); + }); + }); +}; + +const fillDetailsForm = (detectorName, dataSource) => { + getNameField().type(detectorName); + getDataSourceField().selectComboboxItem(dataSource); + getDataSourceField().blur(); + getLogTypeField().selectComboboxItem(cypressLogTypeDns); + getLogTypeField().blur(); +}; + +const createDetector = (detectorName, dataSource, expectFailure) => { + getCreateDetectorButton().click({ force: true }); + + fillDetailsForm(detectorName, dataSource); + + cy.getElementByText( + '.euiAccordion .euiTitle', + 'Detection rules (14 selected)' + ) + .click({ force: true, timeout: 5000 }) + .then(() => cy.contains('.euiTable .euiTableRow', 'Dns')); + + cy.getElementByText( + '.euiAccordion .euiTitle', + 'Configure 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(); + } + }); + + // go to the alerts page + getNextButton().click({ force: true }); + + // TEST ALERTS PAGE + cy.getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); + cy.getInputByPlaceholder('Enter a name to describe the alert condition').type( + 'test_trigger' + ); + cy.getElementByTestSubject('alert-tags-combo-box') + .type(`attack.defense_evasion{enter}`) + .find('input') + .focus() + .blur(); + + cy.getFieldByLabel('Specify alert severity').selectComboboxItem( + '1 (Highest)' + ); + + // go to review page + getNextButton().click({ force: true }); + + // TEST REVIEW AND CREATE PAGE + cy.getElementByText('.euiTitle', 'Review and create'); + cy.getElementByText('.euiTitle', 'Detector details'); + cy.getElementByText('.euiTitle', 'Field mapping'); + cy.getElementByText('.euiTitle', 'Alert triggers'); + + cy.validateDetailsItem('Detector name', detectorName); + cy.validateDetailsItem('Description', '-'); + cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); + cy.validateDetailsItem('Detection rules', '14'); + cy.validateDetailsItem('Created at', '-'); + cy.validateDetailsItem('Last updated time', '-'); + cy.validateDetailsItem( + 'Detector dashboard', + 'Not available for this log type' + ); + + validateAlertPanel('test_trigger'); + + cy.intercept('POST', NODE_API.MAPPINGS_BASE).as('createMappingsRequest'); + cy.intercept('POST', NODE_API.DETECTORS_BASE).as('createDetectorRequest'); + + // create the detector + cy.getElementByText('button', 'Create').click({ force: true }); + + // TEST DETECTOR DETAILS PAGE + cy.wait('@createMappingsRequest'); + + if (!expectFailure) { + cy.wait('@createDetectorRequest').then((interceptor) => { + const detectorId = interceptor.response.body.response._id; + + cy.url() + .should('contain', detectorId) + .then(() => { + cy.getElementByText( + '.euiCallOut', + `Detector created successfully: ${detectorName}` + ); + + // Confirm detector state + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiHealth', 'Active').then(() => { + cy.validateDetailsItem('Detector name', detectorName); + cy.validateDetailsItem('Description', '-'); + cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); + cy.validateDetailsItem('Detection rules', '14'); + cy.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.getElementByText('button.euiTab', 'Alert triggers') + .should('be.visible') + .click(); + validateAlertPanel('test_trigger'); + }); + }); + }); + } +}; + +const openCreateForm = () => getCreateDetectorButton().click({ force: true }); + +const getDescriptionField = () => + cy.getTextareaByLabel('Description - optional'); +const getTriggerNameField = () => cy.getFieldByLabel('Trigger name'); + +describe('Detectors', () => { + before(() => { + cy.cleanUpTests(); + + cy.createIndex(cypressIndexWindows, sample_windows_index_settings); + + // Create test index + cy.createIndex(cypressIndexDns, sample_dns_index_settings).then(() => + cy + .request( + 'POST', + '_plugins/_security_analytics/rules/_search?prePackaged=true', + { + from: 0, + size: 5000, + query: { + nested: { + path: 'rule', + query: { + bool: { must: [{ match: { 'rule.category': 'dns' } }] }, + }, + }, + }, + } + ) + .should('have.property', 'status', 200) + ); + + cy.createRule(dns_name_rule_data); + cy.createRule(dns_type_rule_data); + }); + + describe('...should validate form fields', () => { + beforeEach(() => { + cy.intercept(NODE_API.SEARCH_DETECTORS).as('detectorsSearch'); + + // Visit Detectors page before any test + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); + + openCreateForm(); + }); + + 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'); + }); + + 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'); + }); + + it('...should validate data source field', () => { + getDataSourceField() + .focus() + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .siblings() + .contains('Select an input source.'); + + getDataSourceField().selectComboboxItem(cypressIndexDns); + getDataSourceField() + .focus() + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .should('not.exist'); + }); + + it('...should validate next button', () => { + getNextButton().should('be.disabled'); + + fillDetailsForm(detectorName, cypressIndexDns); + getNextButton().should('be.enabled'); + }); + + it('...should validate alerts page', () => { + fillDetailsForm(detectorName, cypressIndexDns); + getNextButton().click({ force: true }); + getTriggerNameField().should('be.empty'); + + getTriggerNameField().focus().blur(); + getTriggerNameField() + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .contains('Enter a name.'); + + getTriggerNameField().type('Trigger name').focus().blur(); + + getTriggerNameField() + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .should('not.exist'); + + getNextButton().should('be.enabled'); + + getTriggerNameField() + .type('{selectall}') + .type('{backspace}') + .focus() + .blur(); + getNextButton().should('be.disabled'); + + cy.getButtonByText('Remove').click({ force: true }); + getNextButton().should('be.enabled'); + }); + + it('...should show mappings warning', () => { + fillDetailsForm(detectorName, cypressIndexDns); + + getDataSourceField().selectComboboxItem(cypressIndexWindows); + getDataSourceField().focus().blur(); + + 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.' + ); + }); + }); + + describe('...validate create detector flow', () => { + beforeEach(() => { + cy.intercept(NODE_API.SEARCH_DETECTORS) + .as('detectorsSearch') + .as('detectorsSearch'); + + // Visit Detectors page before any test + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); + }); + + it('...can fail creation', () => { + createDetector(`${detectorName}_fail`, '.kibana_1', true); + cy.getElementByText('.euiCallOut', 'Create detector failed.'); + }); + + it('...can be created', () => { + createDetector(detectorName, cypressIndexDns, false); + cy.getElementByText('.euiCallOut', 'Detector created successfully'); + }); + + it('...basic details can be edited', () => { + cy.intercept('GET', NODE_API.INDICES_BASE).as('getIndices'); + openDetectorDetails(detectorName); + + editDetectorDetails(detectorName, 'Detector details'); + + cy.urlShouldContain('edit-detector-details').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector details'); + }); + + cy.wait('@getIndices'); + getNameField() + .type('{selectall}{backspace}') + .type('test detector edited'); + cy.getTextareaByLabel('Description - optional').type( + 'Edited description' + ); + + getDataSourceField().clearCombobox(); + getDataSourceField().selectComboboxItem(cypressIndexWindows); + + cy.getFieldByLabel('Run every').type('{selectall}{backspace}').type('10'); + cy.getFieldByLabel('Run every', 'select').select('Hours'); + + cy.getElementByText('button', 'Save changes').click({ force: true }); + + cy.urlShouldContain('detector-details').then(() => { + cy.validateDetailsItem('Detector name', 'test detector edited'); + cy.validateDetailsItem('Description', 'Edited description'); + cy.validateDetailsItem('Detector schedule', 'Every 10 hours'); + cy.validateDetailsItem('Data source', cypressIndexWindows); + }); + }); + + it('...rules can be edited', () => { + openDetectorDetails(detectorName); + + editDetectorDetails(detectorName, 'Active rules'); + cy.getElementByText('.euiTitle', 'Detection rules (14)'); + + cy.getInputByPlaceholder('Search...') + .type(`${cypressDNSRule}`) + .pressEnterKey(); + + cy.getElementByText('.euiTableCellContent button', cypressDNSRule) + .parents('td') + .prev() + .find('.euiTableCellContent button') + .click(); + + cy.getElementByText('.euiTitle', 'Detection rules (13)'); + cy.getElementByText('button', 'Save changes').click({ force: true }); + cy.urlShouldContain('detector-details').then(() => { + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); + }); + }); + + it('...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); + + editDetectorDetails(detectorName, 'Detector details'); + + cy.urlShouldContain('edit-detector-details').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector details'); + }); + + cy.wait('@getIndices'); + cy.get('.reviewFieldMappings').should('not.exist'); + + getDataSourceField().clearCombobox(); + getDataSourceField().should('not.have.value'); + getDataSourceField().type(`${cypressIndexDns}{enter}`); + + validateFieldMappingsTable('data source is changed'); + + cy.getElementByText('button', 'Save changes').click({ force: true }); + }); + + it('...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.urlShouldContain('edit-detector-rules').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector rules'); + }); + + cy.get('.reviewFieldMappings').should('not.exist'); + + cy.wait('@detectorsSearch'); + + // 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 }); + + 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.getButtonByText('Actions') + .click({ force: true }) + .then(() => { + cy.intercept(NODE_API.DETECTORS_BASE).as('detectors'); + cy.getElementByText('.euiContextMenuItem', 'Delete').click({ + force: true, + }); + cy.wait('@detectors').then(() => { + cy.contains('There are no existing detectors'); + }); + }); + }); + }); + + after(() => cy.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..ac9998122 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,14 +13,11 @@ 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', tags: [ @@ -52,10 +49,7 @@ const YAML_RULE_LINES = [ `- '${SAMPLE_RULE.references}'`, `author: ${SAMPLE_RULE.author}`, `detection:`, - ...SAMPLE_RULE.detection - .replaceAll(' ', '') - .replaceAll('{backspace}', '') - .split('\n'), + ...SAMPLE_RULE.detectionLine, ]; const checkRulesFlyout = () => { @@ -156,204 +150,540 @@ const checkRulesFlyout = () => { }); }; +const getCreateButton = () => cy.get('[data-test-subj="create_rule_button"]'); +const getNameField = () => cy.getFieldByLabel('Rule name'); +const getRuleStatusField = () => cy.getFieldByLabel('Rule Status'); +const getDescriptionField = () => cy.getFieldByLabel('Description - optional'); +const getAuthorField = () => cy.getFieldByLabel('Author'); +const getLogTypeField = () => cy.getFieldByLabel('Log type'); +const getRuleLevelField = () => cy.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().type(SAMPLE_RULE.logType); + getRuleLevelField().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'); + }); + + getConditionAddButton().click({ force: true }); + + // rule additional details + SAMPLE_RULE.tags.forEach((tag, idx) => { + getTagField(idx).type(tag); + idx < SAMPLE_RULE.tags.length - 1 && + cy.getButtonByText('Add tag').click({ force: true }); + }); + + getReferenceFieldByIndex(0).type(SAMPLE_RULE.references); + getFalsePositiveFieldByIndex(0).type(SAMPLE_RULE.falsePositive); +}; + 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'); - }); - it('...can be created', () => { - // Click "create new rule" button - cy.get('[data-test-subj="create_rule_button"]').click({ - force: true, + 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.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().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().containsError('Rule name is required'); + getNameField().type('text').focus().blur(); + getNameField().containsError('Invalid rule name.'); + + getNameField() + .type('{selectall}') + .type('{backspace}') + .type('tex&') + .focus() + .blur(); + getNameField().containsError('Invalid rule name.'); + + getNameField() + .type('{selectall}') + .type('{backspace}') + .type('Rule name') + .focus() + .blur() + .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().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().containsError('Author name is required'); + getAuthorField().type('text').focus().blur(); + getAuthorField().containsError('Invalid author.'); + + getAuthorField() + .type('{selectall}') + .type('{backspace}') + .type('tex&') + .focus() + .blur(); + getAuthorField().containsError('Invalid author.'); + + getAuthorField() + .type('{selectall}') + .type('{backspace}') + .type('Rule name') + .focus() + .blur() + .shouldNotHaveError(); }); - cy.wait('@getRules'); + it('...should validate log type field', () => { + getLogTypeField().should('be.empty'); + getLogTypeField().focus().blur(); + getLogTypeField().containsError('Log type is required'); - cy.contains('Rules'); + getLogTypeField().selectComboboxItem(SAMPLE_RULE.logType); + getLogTypeField().focus().blur().shouldNotHaveError(); + }); - checkRulesFlyout(); - }); + it('...should validate rule level field', () => { + getRuleLevelField().should('be.empty'); + getRuleLevelField().focus().blur(); + getRuleLevelField().containsError('Rule level is required'); - it('...can be edited', () => { - cy.contains('Rules'); + getRuleLevelField().selectComboboxItem(SAMPLE_RULE.severity); + getRuleLevelField().focus().blur().shouldNotHaveError(); + }); + + it('...should validate rule status field', () => { + getRuleStatusField().containsValue(SAMPLE_RULE.status); + getRuleStatusField().focus().blur().shouldNotHaveError(); + + getRuleStatusField().clearCombobox(); + getRuleStatusField().focus().blur(); + getRuleStatusField().containsError('Rule status is required'); + }); + + it('...should validate selection', () => { + getSelectionPanelByIndex(0).within(() => { + getSelectionNameField().should('have.value', 'Selection_1'); + getSelectionNameField().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('...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).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().clearValue(); + toastShouldExist(); + getNameField().type('Rule name'); + + // author field + getAuthorField().clearValue(); + toastShouldExist(); + getAuthorField().type('John Doe'); + + // log field + getLogTypeField().clearCombobox(); + toastShouldExist(); + getLogTypeField().selectComboboxItem(SAMPLE_RULE.logType); + + // severity field + getRuleLevelField().clearCombobox(); + toastShouldExist(); + getRuleLevelField().selectComboboxItem(SAMPLE_RULE.severity); + + // status field + getRuleStatusField().clearCombobox(); + toastShouldExist(); + getRuleStatusField().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().clearValue(); + }); + toastShouldExist(); + getSelectionPanelByIndex(0).within(() => { + getListRadioField().click({ force: true }); + getMapListField().type('FieldValue'); + }); - checkRulesFlyout(); + // condition field + getConditionRemoveButton(0).click({ force: true }); + toastShouldExist(); + getConditionAddButton().click({ force: true }); + + // tags field + getTagField(0).clearValue().type('wrong.tag'); + toastShouldExist(); + getTagField(0).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.waitForPageLoad('rules', { + contains: 'Detection rules', + }); + }); + + it('...can be created', () => { + getCreateButton().click({ force: true }); + + fillCreateForm(); - cy.intercept(`${NODE_API.RULES_SEARCH}?prePackaged=false`, { - delay: 5000, - }).as('getCustomRules'); + // Switch to YAML editor + cy.get('[data-test-subj="change-editor-type"] label:nth-child(2)').click({ + force: true, + }); - cy.get(`input[placeholder="Search rules"]`).ospSearch(SAMPLE_RULE.name); + YAML_RULE_LINES.forEach((line) => + cy.get('[data-test-subj="rule_yaml_editor"]').contains(line) + ); - // Click the rule link to open the details flyout - cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({ - force: true, + cy.intercept({ + url: `${NODE_API.RULES_BASE}/_search?prePackaged=false`, + }).as('getRules'); + + submitRule(); + + cy.wait('@getRules'); + + cy.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.waitForPageLoad('rules', { + contains: 'Detection rules', + }); - cy.wait('@getCustomRules'); - cy.wait('@getPrePackagedRules'); + cy.get(`input[placeholder="Search rules"]`).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(); + }); - // 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'); + getNameField().clear(); + + SAMPLE_RULE.name += ' edited'; + getNameField().type(SAMPLE_RULE.name); + getNameField().should('have.value', SAMPLE_RULE.name); + + getLogTypeField().clearCombobox(); + SAMPLE_RULE.logType = 'dns'; + YAML_RULE_LINES[2] = `product: ${SAMPLE_RULE.logType}`; + YAML_RULE_LINES[3] = `title: ${SAMPLE_RULE.name}`; + getLogTypeField().type(SAMPLE_RULE.logType).type('{enter}'); + getLogTypeField() + .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.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"]`).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"]`).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()); 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..feb39989d 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.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('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,16 +253,14 @@ 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"}' - ), + JSON.parse('{"winlog.event_id": 2003}'), null, 2 ); @@ -350,7 +299,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); }); }); @@ -384,21 +333,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,7 +365,7 @@ describe('Alerts', () => { }); cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) + .filter(`:contains(${alertName})`) .should('have.length', 3); cy.get('tbody > tr') @@ -420,11 +376,12 @@ describe('Alerts', () => { }); cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) + .filter(`:contains(${alertName})`) .should('have.length', 2); + cy.wait(10000); + // 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,7 +389,7 @@ describe('Alerts', () => { // Confirm there are now 3 "Acknowledged" alerts cy.get('tbody > tr') - .filter(`:contains(${testDetectorAlertCondition})`) + .filter(`:contains(${alertName})`) .should('have.length', 2); }); @@ -484,7 +441,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,7 +463,7 @@ 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 ); }); 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..462c83fa2 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.waitForPageLoad('findings', { + contains: 'Findings', + }); + + cy.wait(5000); }); it('displays findings based on recently ingested data', () => { @@ -51,14 +55,13 @@ 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('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"]`).ospSearch(indexName); // Click View details icon cy.getTableFirstRow('[data-test-subj="view-details-icon"]').then(($el) => { @@ -77,7 +80,7 @@ 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"]`).ospSearch(indexName); // Click findingId to trigger Finding details flyout cy.getTableFirstRow( @@ -98,7 +101,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"]`).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 +114,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,7 +128,7 @@ 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"]`).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) => { @@ -135,61 +137,14 @@ describe('Findings', () => { // 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); }); }); diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js index cfecd118f..d0de8fcfc 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js @@ -3,9 +3,70 @@ * 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'); + +// *********************************************** +// 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) => { ... }) + +// Be able to add default options to cy.request(), https://github.com/cypress-io/cypress/issues/726 +Cypress.Commands.overwrite('request', (originalFn, ...args) => { + let defaults = {}; + // Add the basic authentication header when security enabled in the Opensearch cluster + const ADMIN_AUTH = { + username: Cypress.env('username'), + password: Cypress.env('password'), + }; + if (Cypress.env('SECURITY_ENABLED')) { + defaults.auth = ADMIN_AUTH; + } + + let options = {}; + if (typeof args[0] === 'object' && args[0] !== null) { + options = Object.assign({}, args[0]); + } else if (args.length === 1) { + [options.url] = args; + } else if (args.length === 2) { + [options.method, options.url] = args; + } else if (args.length === 3) { + [options.method, options.url, options.body] = args; + } + + Object.assign(options, { + headers: { + 'osd-xsrf': '', + }, + }); + + return originalFn(Object.assign({}, defaults, options)); +}); Cypress.Commands.add('cleanUpTests', () => { cy.deleteAllCustomRules(); @@ -17,3 +78,473 @@ Cypress.Commands.add('getTableFirstRow', (selector) => { if (!selector) return cy.get('tbody > tr').first(); return cy.get('tbody > tr:first').find(selector); }); + +Cypress.Commands.add( + '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 }) + .should('include', fullUrl) + .then(() => { + contains && cy.contains(contains).should('be.visible'); + }); + } +); + +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('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('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('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('getButtonByText', (text) => { + Cypress.log({ message: `Get button by text: ${text}` }); + return cy.getElementByText('.euiButton', text); +}); + +Cypress.Commands.add('getInputByPlaceholder', (placeholder) => { + Cypress.log({ message: `Get input element by placeholder: ${placeholder}` }); + return cy.get(`input[placeholder="${placeholder}"]`); +}); + +Cypress.Commands.add('getComboboxByPlaceholder', (placeholder) => { + Cypress.log({ + message: `Get combobox element by placeholder: ${placeholder}`, + }); + return cy + .getElementByText('.euiComboBoxPlaceholder', placeholder) + .siblings('.euiComboBox__input') + .find('input'); +}); + +Cypress.Commands.add('getFieldByLabel', (label, type = 'input') => { + Cypress.log({ message: `Get field by label: ${label}` }); + return cy + .getElementByText('.euiFormRow__labelWrapper', label) + .siblings() + .find(type); +}); + +Cypress.Commands.add('getTextareaByLabel', (label) => { + Cypress.log({ message: `Get textarea by label: ${label}` }); + return cy.getFieldByLabel(label, 'textarea'); +}); + +Cypress.Commands.add('getElementByTestSubject', (subject) => { + Cypress.log({ message: `Get element by test subject: ${subject}` }); + return cy.get(`[data-test-subj="${subject}"]`); +}); + +Cypress.Commands.add('getRadioButtonById', (id) => { + Cypress.log({ message: `Get radio button by id: ${id}` }); + return cy.get(`input[id="${id}"]`); +}); + +Cypress.Commands.add( + '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).type('{enter}')); + } +); + +Cypress.Commands.add( + '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( + 'containsValue', + { + prevSubject: true, + }, + (subject, value) => + cy.wrap(subject).parents('.euiFormRow__fieldWrapper').contains(value, { + matchCase: false, + }) +); + +Cypress.Commands.add( + 'clearValue', + { + prevSubject: true, + }, + (subject) => cy.wrap(subject).type('{selectall}').type('{backspace}') +); + +Cypress.Commands.add( + 'containsError', + { + prevSubject: true, + }, + (subject, errorText) => + cy + .wrap(subject) + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .contains(errorText) +); + +Cypress.Commands.add( + 'containsHelperText', + { + prevSubject: true, + }, + (subject, helperText) => + cy + .wrap(subject) + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormHelpText') + .contains(helperText) +); + +Cypress.Commands.add( + 'shouldNotHaveError', + { + prevSubject: true, + }, + (subject) => + cy + .wrap(subject) + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .should('not.exist') +); + +Cypress.Commands.add('validateDetailsItem', (label, value) => { + Cypress.log({ + message: `Validate details item by label: ${label} and value: ${value}`, + }); + return cy + .getElementByText('.euiFlexItem label', label) + .parent() + .siblings() + .contains(value); +}); + +Cypress.Commands.add('urlShouldContain', (path) => { + Cypress.log({ message: `Url should contain path: ${path}` }); + return cy.url().should('contain', `#/${path}`); +}); + +Cypress.Commands.add( + '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( + '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('createIndex', (index, settings = {}) => { + cy.request('PUT', `${BACKEND_BASE_PATH}/${index}`, settings).should( + 'have.property', + 'status', + 200 + ); +}); + +Cypress.Commands.add('createIndexTemplate', (name, template) => { + cy.request( + 'PUT', + `${BACKEND_BASE_PATH}${NODE_API.INDEX_TEMPLATE_BASE}/${name}`, + template + ); +}); + +Cypress.Commands.add('ingestDocument', (indexId, documentJSON) => { + cy.request('POST', `${BACKEND_BASE_PATH}/${indexId}/_doc`, documentJSON); +}); + +Cypress.Commands.add( + 'insertDocumentToIndex', + (indexName, documentId, documentBody) => { + cy.request({ + method: 'POST', + url: `${BACKEND_BASE_PATH}/${indexName}/_doc/${documentId}`, + body: documentBody, + }); + } +); + +Cypress.Commands.add('deleteIndex', (indexName, options = {}) => { + cy.request({ + method: 'DELETE', + url: `${BACKEND_BASE_PATH}/${indexName}`, + failOnStatusCode: false, + ...options, + }); +}); + +Cypress.Commands.add('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('createRule', (ruleJSON) => { + return cy.request({ + method: 'POST', + url: `${OPENSEARCH_DASHBOARDS}${NODE_API.RULES_BASE}?category=${ruleJSON.category}`, + body: JSON.stringify(ruleJSON), + }); +}); + +Cypress.Commands.add('updateRule', (ruleId, ruleJSON) => { + cy.request( + 'PUT', + `${BACKEND_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: `${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('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( + 'ospSearch', + { + prevSubject: true, + }, + (subject, text) => { + return cy.get(subject).clear().ospType(text).realPress('Enter'); + } +); + +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); + } +); + +Cypress.Commands.add( + '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; + } +); diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js index 1b6c46edf..a0f510043 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,92 @@ 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.cleanUpTests() + // Create test index + .then(() => cy.createIndex(indexName, indexSettings)) + + // Create field mappings + .then(() => + cy.createAliasMappings( + indexName, + detectorConfig.detector_type, + indexMappings, + true + ) + ) + // Create rule + .then(() => { + cy.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.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); + }); + }); + + // Ingest documents to the test index + for (let i = 0; i < indexDocsCount; i++) { + cy.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..1225c9c59 --- /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.getElementByText('.euiTitle', 'Some title') + */ + getElementByText(locator: string, text: string): Chainable; + + /** + * Returns button by its text + * @example + * cy.getButtonByText('Button text') + */ + getButtonByText(text: string): Chainable; + + /** + * Returns input by its placeholder + * @example + * cy.getInputByPlaceholder('Search rules...') + */ + getInputByPlaceholder(placeholder: string): Chainable; + + /** + * Returns combobox input by its placeholder + * @example + * cy.getComboboxByPlaceholder('Select data input...') + */ + getComboboxByPlaceholder(placeholder: string): Chainable; + + /** + * Returns field input by label + * @example + * cy.getFieldByLabel('Detector name') + */ + getFieldByLabel(label: string, type?: string): Chainable; + + /** + * Returns textarea by label + * @example + * cy.getTextareaByLabel('Detector description') + */ + getTextareaByLabel(label: string): Chainable; + + /** + * Returns element by data-test-subj attribute value + * @example + * cy.getElementByTestSubject('alerts-input-element') + */ + getElementByTestSubject(subject: string): Chainable; + + /** + * Returns radio by id + * @example + * cy.getRadioButtonById('radioId') + */ + getRadioButtonById(id: string): Chainable; + + /** + * Selects combobox item(s) + * @example + * cy.get('combo).selectComboboxItem('some item value') + */ + selectComboboxItem(items: string | string[]): Chainable; + + /** + * Clears combobox value(s) + * @example + * cy.get('combo).clearCombobox() + */ + clearCombobox(): Chainable; + + /** + * Triggers enter key event on the focused element + * @example + * cy.pressEnterKey() + */ + pressEnterKey(): Chainable; + + /** + * Triggers backspace key event on the focused element + * @example + * cy.pressBackspaceKey() + */ + pressBackspaceKey(numberOfPresses?: number): Chainable; + + /** + * Validates details panel item + * @example + * cy.validateDetailsItem('Data source', '.index-name') + */ + validateDetailsItem(label: string, value: string): Chainable; + + /** + * Should clear a field value (use with text and textarea fields) + * @example + * cy.getFieldByLabel('Rule name').clearValue() + */ + 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.getFieldByLabel('Rule name').containsValue('Name') + */ + containsValue(value: string): Chainable; + + /** + * Validates that field has error text + * @example + * cy.getFieldByLabel('Rule name').containsError('This fields is invalid') + */ + containsError(errorText: string): Chainable; + + /** + * Validates that field has helper text + * @example + * cy.getFieldByLabel('Rule name').containsHelperText('Use this field for...') + */ + containsHelperText(helperText: string): Chainable; + + /** + * Should not have error text + * @example + * cy.getFieldByLabel('Rule name').shouldNotHaveError() + */ + shouldNotHaveError(): Chainable; + + /** + * Validates url path + * @example + * cy.urlShouldContain('/detector-details') + */ + urlShouldContain(path: string): Chainable; + + /** + * Validates table items + * @example + * cy.validateTable('/detector-details') + */ + validateTable(data: { [key: string]: string }[]): Chainable; + + /** + * Removes custom indices, detectors and rules + * @example + * cy.cleanUpTests() + */ + cleanUpTests(): Chainable; + + /** + * Returns table first row + * Finds elements deeper in a row with selector + * @param {string} selector + * @example + * cy.getTableFirstRow() + * cy.getTableFirstRow('td') + */ + getTableFirstRow(selector: string): Chainable; + + /** + * Waits for page to be loaded + * @param {string} pathname + * @param {any} opts + * @example + * cy.waitForPageLoad('detectors') + * cy.waitForPageLoad('detectors', { + * timeout: 20000, + * contains: 'text to verify' + * }) + */ + 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').ospSearch('Txt to write into input') + */ + ospSearch(text: string): Chainable; + + /** + * Clears input text + * @example + * cy.get('selector').ospClear() + */ + ospClear(): Chainable; + + /** + * Returns table first row + * Can find elements deeper in a row with selector + * @param {string} text + * @example + * cy.get('selector').ospType('Txt to write into input') + */ + ospType(text: string): Chainable; + + /** + * Creates index with policy + * @example + * cy.createIndex("some_index", "some_policy") + */ + createIndex(index: string, settings?: object): Chainable; + + /** + * Creates an index template. + * @example + * cy.createIndexTemplate("some_index_template", { "index_patterns": "abc", "properties": { ... } }) + */ + createIndexTemplate(name: string, template: object): Chainable; + + /** + /** + * Deletes all indices in cluster + * @example + * cy.deleteAllIndices() + */ + deleteAllIndices(): Chainable; + + /** + * Deletes all custom rules in cluster + * @example + * cy.deleteAllCustomRules() + */ + deleteAllCustomRules(): Chainable; + + /** + * Deletes all detectors in cluster + * @example + * cy.deleteAllDetectors() + */ + deleteAllDetectors(): Chainable; + + /** + * Creates a detector + * @example + * cy.createPolicy({ "detector_type": ... }) + */ + createDetector(detectorJSON: object): Chainable; + + /** + * Creates a fields mapping aliases for detector + * @example + * cy.createAliasMappings('indexName', 'windows', {...}, true) + */ + createAliasMappings( + indexName: string, + ruleTopic: string, + aliasMappingsBody: object, + partial: boolean + ): Chainable; + + /** + * Creates a custom rule + * @example + * cy.createRule({}) + */ + createRule(ruleJSON: object): Chainable; + + /** + * Updates settings for index + * @example + * cy.updateIndexSettings("some_index", settings) + */ + updateDetector(detectorId: string, detectorJSON: object): Chainable; + + /** + * Deletes detector by its name + * @example + * cy.deleteDetector("Cypress detector name") + */ + deleteDetector(name: string): Chainable; + } +} diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.js new file mode 100644 index 000000000..435865356 --- /dev/null +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.js @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Import commands.js using ES2015 syntax: +import './commands'; +import 'cypress-real-events'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; +Cypress.on('uncaught:exception', (err) => { + /* returning false here prevents Cypress from failing the test */ + if (resizeObserverLoopErrRe.test(err.message)) { + return false; + } +}); + +// Switch the base URL of Opensearch when security enabled in the cluster +// Not doing this for Dashboards because it can still use http when security enabled +if (Cypress.env('SECURITY_ENABLED')) { + Cypress.env('opensearch', `https://${Cypress.env('openSearchUrl')}`); +} else { + Cypress.env('opensearch', `http://${Cypress.env('openSearchUrl')}`); +} 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'); - } -); From 7832d58b8a316bf4f31b2b08ddfc876a585f5ffd Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 6 Sep 2023 15:08:17 -0700 Subject: [PATCH 2/9] excluded a couple tests; fixed alerts tests Signed-off-by: Amardeepsingh Siglani --- .../1_detectors.spec.js | 1008 ++++++++++------- .../1_detectors_new.spec.js | 601 ---------- .../3_alerts.spec.js | 14 +- 3 files changed, 605 insertions(+), 1018 deletions(-) delete mode 100644 cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js 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 869a6f1e9..997b56ab7 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,415 +3,599 @@ * 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'; - -// const testMappings = { -// properties: { -// 'dns-question-name': { -// type: 'alias', -// path: 'DnsQuestionName', -// }, -// }, -// }; - -// const cypressDNSRule = dns_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 }); - -// // Check to ensure process started -// cy.contains('Define detector'); - -// // Enter a name for the detector in the appropriate input -// cy.get(`input[placeholder="Enter a name for the detector."]`) -// .focus() -// .realType(detectorName); - -// // 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, -// }); - -// cy.contains('table tr', 'DNS', { -// timeout: 120000, -// }); -// }); - -// // Check that correct page now showing -// cy.contains('Configure field mapping'); - -// if (!expectFailure) { -// // Select appropriate names to map fields to -// for (let field_name in testMappings.properties) { -// const mappedTo = testMappings.properties[field_name].path; - -// cy.contains('tr', field_name).within(() => { -// cy.get(`[data-test-subj="detector-field-mappings-select"]`) -// .click() -// .type(mappedTo); -// }); -// } -// } - -// // 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'); - -// // Select applicable severity levels -// cy.get(`[data-test-subj="security-levels-combo-box"]`).click({ force: true }); -// cy.contains('1 (Highest)').click({ force: true }); - -// // Continue to next page -// cy.contains('Next').click({ force: true }); - -// // Confirm page is reached -// cy.contains('Review and create'); - -// // Confirm field mappings registered -// cy.contains('Field mapping'); - -// if (!expectFailure) { -// for (let field in testMappings.properties) { -// const mappedTo = testMappings.properties[field].path; - -// cy.contains(field); -// cy.contains(mappedTo); -// } -// } - -// // Confirm entries user has made -// cy.contains('Detector details'); -// cy.contains(detectorName); -// cy.contains('dns'); -// cy.contains('test_trigger'); - -// // Create the detector -// cy.get('button').contains('Create').click({ force: true }); -// cy.contains(detectorName); - -// cy.contains('Attempting to create the detector.'); - -// // Confirm detector active -// cy.contains(detectorName); -// cy.contains('Active'); - -// if (!expectFailure) { -// cy.contains('Actions'); -// } - -// 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'; - -// before(() => { -// cy.cleanUpTests(); - -// cy.createIndex(cypressIndexWindows, null, sample_index_settings); - -// // Create test index -// cy.createIndex(cypressIndexDns, null, sample_index_settings).then(() => -// cy -// .request({ -// method: 'POST', -// url: `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=true`, -// headers: { -// 'osd-xsrf': true, -// }, -// body: { -// from: 0, -// size: 5000, -// query: { -// nested: { -// path: 'rule', -// query: { -// bool: { must: [{ match: { 'rule.category': 'dns' } }] }, -// }, -// }, -// }, -// }, -// }) -// .should('have.property', 'status', 200) -// ); - -// cy.createRule(dns_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'); - -// // Check that correct page is showing -// cy.contains('Threat detectors'); -// }); - -// 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.'); -// }); - -// it('...basic details can be edited', () => { -// // Click on detector name -// cy.contains(detectorName).click({ force: true }); -// cy.contains('Detector details'); -// cy.contains(detectorName); - -// // Click "Edit" button in detector details -// cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ -// force: true, -// }); - -// // 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, -// }); - -// // Confirm taken to detector details page -// cy.contains(detectorName); - -// // Verify edits are applied -// cy.contains('test detector edited'); -// cy.contains('Every 10 hours'); -// cy.contains('Edited description'); -// cy.contains(cypressIndexWindows); -// }); - -// it('...rules can be edited', () => { -// // Ensure start on main detectors page -// cy.contains('Threat detectors'); - -// // Click on detector name -// cy.contains(detectorName).click({ force: true }); -// cy.contains('Detector details'); -// cy.contains(detectorName); - -// // Confirm number of rules before edit -// cy.contains('Active rules (13)'); - -// // Click "Edit" button in Detector rules panel -// cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - -// // Confirm arrival on "Edit detector rules" page -// cy.contains('Edit detector rules'); - -// // Search for specific rule -// cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - -// // 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 }); -// }); - -// // Save changes -// cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ -// force: true, -// }); - -// // Confirm 1 rule has been removed from detector -// cy.contains('Active rules (12)'); - -// // Click "Edit" button in Detector rules panel -// cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - -// // Confirm arrival on "Edit detector rules" page -// cy.contains('Edit detector rules'); - -// // Search for specific rule -// cy.get(`input[placeholder="Search..."]`).ospSearch(cypressDNSRule); - -// // Toggle single search result to checked -// cy.contains('table tr', cypressDNSRule).within(() => { -// cy.wait(2000); -// cy.get('button').eq(1).click({ force: true }); -// }); - -// // Save changes -// cy.get(`[data-test-subj="save-detector-rules-edits"]`).click({ -// force: true, -// }); -// cy.contains(detectorName); - -// // Confirm 1 rule has been added to detector -// cy.contains('Active rules (13)'); -// }); - -// 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); - -// // Click "Edit" button in detector details -// cy.get(`[data-test-subj="edit-detector-basic-details"]`).click({ -// force: true, -// }); - -// // Confirm arrival at "Edit detector details" page -// cy.contains('Edit detector details'); - -// cy.get('.reviewFieldMappings').should('not.exist'); - -// // 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'); -// }); - -// 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); - -// // Click "Edit" button in detector details -// cy.get(`[data-test-subj="edit-detector-rules"]`).click({ force: true }); - -// // Confirm arrival at "Edit detector details" page -// cy.contains('Edit detector rules'); - -// cy.get('.reviewFieldMappings').should('not.exist'); - -// cy.intercept(NODE_API.MAPPINGS_VIEW).as('getMappingsView'); - -// cy.get('table th').within(() => { -// cy.get('button').first().click({ force: true }); -// }); - -// cy.get('.reviewFieldMappings').should('be.visible'); -// }); - -// it('...can be deleted', () => { -// // Click on detector to be removed -// cy.contains('test detector edited').click({ force: true }); - -// // Confirm page -// cy.contains('Detector details'); - -// // Click "Actions" button, the click "Delete" -// cy.get('button').contains('Actions').click({ force: true }); -// cy.get('button').contains('Delete').click({ force: true }); - -// // Confirm detector is deleted -// cy.contains('There are no existing detectors'); -// }); - -// after(() => cy.cleanUpTests()); -// }); +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'; + +const cypressIndexDns = 'cypress-index-dns'; +const cypressIndexWindows = 'cypress-index-windows'; +const detectorName = 'test detector'; +const cypressLogTypeDns = 'dns'; + +const cypressDNSRule = dns_name_rule_data.title; + +const getNameField = () => + cy.getInputByPlaceholder('Enter a name for the detector.'); + +const getNextButton = () => cy.getButtonByText('Next'); + +const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); + +const validateAlertPanel = (alertName) => + cy + .getElementByText('.euiTitle', 'Alert triggers') + .parentsUntil('.euiPanel') + .siblings() + .eq(2) + .within(() => cy.getElementByText('button', alertName)); + +const dataSourceLabel = 'Select or input source indexes or index patterns'; + +const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel); + +const logTypeLabel = 'Select a log type you would like to detect'; + +const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel); + +const openDetectorDetails = (detectorName) => { + cy.getInputByPlaceholder('Search threat detectors') + .type(`${detectorName}`) + .pressEnterKey(); + cy.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; +}; + +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); + } + }); + }); +}; + +const editDetectorDetails = (detectorName, panelTitle) => { + cy.urlShouldContain('detector-details').then(() => { + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiPanel .euiTitle', panelTitle); + cy.getElementByText('.euiPanel .euiTitle', panelTitle) + .parent() + .siblings() + .within(() => cy.get('button').contains('Edit').click()); + }); +}; + +const validateAutomaticFieldMappingsPanel = (mappings) => + cy.get('.editFieldMappings').within(() => { + cy.get('.euiAccordion__triggerWrapper button').then(($btn) => { + cy.get($btn).contains(`Automatically mapped fields (${mappings.length})`); + + // 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.getElementByTestSubject('auto-mapped-fields-table') + .find('.euiBasicTable') + .validateTable(mappings); + }); + } + }); + }); + +const validatePendingFieldMappingsPanel = (mappings) => { + cy.get('.editFieldMappings').within(() => { + // Pending field mappings + cy.getElementByText('.euiTitle', 'Pending field mappings') + .parents('.euiPanel') + .within(() => { + cy.getElementByTestSubject('pending-mapped-fields-table') + .find('.euiBasicTable') + .validateTable(mappings); + }); + }); +}; + +const fillDetailsForm = (detectorName, dataSource) => { + getNameField().type(detectorName); + getDataSourceField().selectComboboxItem(dataSource); + getDataSourceField().blur(); + getLogTypeField().selectComboboxItem(cypressLogTypeDns); + getLogTypeField().blur(); +}; + +const createDetector = (detectorName, dataSource, expectFailure) => { + getCreateDetectorButton().click({ force: true }); + + fillDetailsForm(detectorName, dataSource); + + cy.getElementByText( + '.euiAccordion .euiTitle', + 'Detection rules (14 selected)' + ) + .click({ force: true, timeout: 5000 }) + .then(() => cy.contains('.euiTable .euiTableRow', 'Dns')); + + cy.getElementByText( + '.euiAccordion .euiTitle', + 'Configure 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(); + } + }); + + // go to the alerts page + getNextButton().click({ force: true }); + + // TEST ALERTS PAGE + cy.getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); + cy.getInputByPlaceholder('Enter a name to describe the alert condition').type( + 'test_trigger' + ); + cy.getElementByTestSubject('alert-tags-combo-box') + .type(`attack.defense_evasion{enter}`) + .find('input') + .focus() + .blur(); + + cy.getFieldByLabel('Specify alert severity').selectComboboxItem( + '1 (Highest)' + ); + + // go to review page + getNextButton().click({ force: true }); + + // TEST REVIEW AND CREATE PAGE + cy.getElementByText('.euiTitle', 'Review and create'); + cy.getElementByText('.euiTitle', 'Detector details'); + cy.getElementByText('.euiTitle', 'Field mapping'); + cy.getElementByText('.euiTitle', 'Alert triggers'); + + cy.validateDetailsItem('Detector name', detectorName); + cy.validateDetailsItem('Description', '-'); + cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); + cy.validateDetailsItem('Detection rules', '14'); + cy.validateDetailsItem('Created at', '-'); + cy.validateDetailsItem('Last updated time', '-'); + cy.validateDetailsItem( + 'Detector dashboard', + 'Not available for this log type' + ); + + validateAlertPanel('test_trigger'); + + cy.intercept('POST', NODE_API.MAPPINGS_BASE).as('createMappingsRequest'); + cy.intercept('POST', NODE_API.DETECTORS_BASE).as('createDetectorRequest'); + + // create the detector + cy.getElementByText('button', 'Create').click({ force: true }); + + // TEST DETECTOR DETAILS PAGE + cy.wait('@createMappingsRequest'); + + if (!expectFailure) { + cy.wait('@createDetectorRequest').then((interceptor) => { + const detectorId = interceptor.response.body.response._id; + + cy.url() + .should('contain', detectorId) + .then(() => { + cy.getElementByText( + '.euiCallOut', + `Detector created successfully: ${detectorName}` + ); + + // Confirm detector state + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiHealth', 'Active').then(() => { + cy.validateDetailsItem('Detector name', detectorName); + cy.validateDetailsItem('Description', '-'); + cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); + cy.validateDetailsItem('Detection rules', '14'); + cy.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.getElementByText('button.euiTab', 'Alert triggers') + .should('be.visible') + .click(); + validateAlertPanel('test_trigger'); + }); + }); + }); + } +}; + +const openCreateForm = () => getCreateDetectorButton().click({ force: true }); + +const getDescriptionField = () => + cy.getTextareaByLabel('Description - optional'); +const getTriggerNameField = () => cy.getFieldByLabel('Trigger name'); + +describe('Detectors', () => { + before(() => { + cy.cleanUpTests(); + + cy.createIndex(cypressIndexWindows, sample_windows_index_settings); + + // Create test index + cy.createIndex(cypressIndexDns, sample_dns_index_settings).then(() => + cy + .request( + 'POST', + '_plugins/_security_analytics/rules/_search?prePackaged=true', + { + from: 0, + size: 5000, + query: { + nested: { + path: 'rule', + query: { + bool: { must: [{ match: { 'rule.category': 'dns' } }] }, + }, + }, + }, + } + ) + .should('have.property', 'status', 200) + ); + + cy.createRule(dns_name_rule_data); + cy.createRule(dns_type_rule_data); + }); + + describe('...should validate form fields', () => { + beforeEach(() => { + cy.intercept(NODE_API.SEARCH_DETECTORS).as('detectorsSearch'); + + // Visit Detectors page before any test + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); + + openCreateForm(); + }); + + 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'); + }); + + 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'); + }); + + it('...should validate data source field', () => { + getDataSourceField() + .focus() + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .siblings() + .contains('Select an input source.'); + + getDataSourceField().selectComboboxItem(cypressIndexDns); + getDataSourceField() + .focus() + .blur() + .parentsUntil('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .should('not.exist'); + }); + + it('...should validate next button', () => { + getNextButton().should('be.disabled'); + + fillDetailsForm(detectorName, cypressIndexDns); + getNextButton().should('be.enabled'); + }); + + it('...should validate alerts page', () => { + fillDetailsForm(detectorName, cypressIndexDns); + getNextButton().click({ force: true }); + getTriggerNameField().should('be.empty'); + + getTriggerNameField().focus().blur(); + getTriggerNameField() + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .contains('Enter a name.'); + + getTriggerNameField().type('Trigger name').focus().blur(); + + getTriggerNameField() + .parents('.euiFormRow__fieldWrapper') + .find('.euiFormErrorText') + .should('not.exist'); + + getNextButton().should('be.enabled'); + + getTriggerNameField() + .type('{selectall}') + .type('{backspace}') + .focus() + .blur(); + getNextButton().should('be.disabled'); + + cy.getButtonByText('Remove').click({ force: true }); + getNextButton().should('be.enabled'); + }); + + it('...should show mappings warning', () => { + fillDetailsForm(detectorName, cypressIndexDns); + + getDataSourceField().selectComboboxItem(cypressIndexWindows); + getDataSourceField().focus().blur(); + + 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.' + ); + }); + }); + + describe('...validate create detector flow', () => { + beforeEach(() => { + cy.intercept(NODE_API.SEARCH_DETECTORS) + .as('detectorsSearch') + .as('detectorsSearch'); + + // Visit Detectors page before any test + cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); + cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); + }); + + it('...can fail creation', () => { + createDetector(`${detectorName}_fail`, '.kibana_1', true); + cy.getElementByText('.euiCallOut', 'Create detector failed.'); + }); + + it('...can be created', () => { + createDetector(detectorName, cypressIndexDns, false); + cy.getElementByText('.euiCallOut', 'Detector created successfully'); + }); + + it('...basic details can be edited', () => { + cy.intercept('GET', NODE_API.INDICES_BASE).as('getIndices'); + openDetectorDetails(detectorName); + + editDetectorDetails(detectorName, 'Detector details'); + + cy.urlShouldContain('edit-detector-details').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector details'); + }); + + cy.wait('@getIndices'); + getNameField() + .type('{selectall}{backspace}') + .type('test detector edited'); + cy.getTextareaByLabel('Description - optional').type( + 'Edited description' + ); + + getDataSourceField().clearCombobox(); + getDataSourceField().selectComboboxItem(cypressIndexWindows); + + cy.getFieldByLabel('Run every').type('{selectall}{backspace}').type('10'); + cy.getFieldByLabel('Run every', 'select').select('Hours'); + + cy.getElementByText('button', 'Save changes').click({ force: true }); + + cy.urlShouldContain('detector-details').then(() => { + cy.validateDetailsItem('Detector name', 'test detector edited'); + cy.validateDetailsItem('Description', 'Edited description'); + cy.validateDetailsItem('Detector schedule', 'Every 10 hours'); + cy.validateDetailsItem('Data source', cypressIndexWindows); + }); + }); + + it('...rules can be edited', () => { + openDetectorDetails(detectorName); + + editDetectorDetails(detectorName, 'Active rules'); + cy.getElementByText('.euiTitle', 'Detection rules (14)'); + + cy.getInputByPlaceholder('Search...') + .type(`${cypressDNSRule}`) + .pressEnterKey(); + + cy.getElementByText('.euiTableCellContent button', cypressDNSRule) + .parents('td') + .prev() + .find('.euiTableCellContent button') + .click(); + + cy.getElementByText('.euiTitle', 'Detection rules (13)'); + cy.getElementByText('button', 'Save changes').click({ force: true }); + cy.urlShouldContain('detector-details').then(() => { + cy.getElementByText('.euiTitle', detectorName); + cy.getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); + }); + }); + + 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); + + editDetectorDetails(detectorName, 'Detector details'); + + cy.urlShouldContain('edit-detector-details').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector details'); + }); + + cy.wait('@getIndices'); + cy.get('.reviewFieldMappings').should('not.exist'); + + getDataSourceField().clearCombobox(); + getDataSourceField().should('not.have.value'); + getDataSourceField().type(`${cypressIndexDns}{enter}`); + + validateFieldMappingsTable('data source is changed'); + + cy.getElementByText('button', 'Save changes').click({ force: true }); + }); + + 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.urlShouldContain('edit-detector-rules').then(() => { + cy.getElementByText('.euiTitle', 'Edit detector rules'); + }); + + cy.get('.reviewFieldMappings').should('not.exist'); + + cy.wait('@detectorsSearch'); + + // 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 }); + + 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.getButtonByText('Actions') + .click({ force: true }) + .then(() => { + cy.intercept(`${NODE_API.DETECTORS_BASE}/_search`).as('detectors'); + cy.getElementByText('.euiContextMenuItem', 'Delete').click({ + force: true, + }); + cy.wait('@detectors').then(() => { + cy.contains('There are no existing detectors'); + }); + }); + }); + }); + + after(() => cy.cleanUpTests()); +}); diff --git a/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js b/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js deleted file mode 100644 index b47b98c7b..000000000 --- a/cypress/integration/plugins/security-analytics-dashboards-plugin/1_detectors_new.spec.js +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -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'; - -const cypressIndexDns = 'cypress-index-dns'; -const cypressIndexWindows = 'cypress-index-windows'; -const detectorName = 'test detector'; -const cypressLogTypeDns = 'dns'; - -const cypressDNSRule = dns_name_rule_data.title; - -const getNameField = () => - cy.getInputByPlaceholder('Enter a name for the detector.'); - -const getNextButton = () => cy.getButtonByText('Next'); - -const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); - -const validateAlertPanel = (alertName) => - cy - .getElementByText('.euiTitle', 'Alert triggers') - .parentsUntil('.euiPanel') - .siblings() - .eq(2) - .within(() => cy.getElementByText('button', alertName)); - -const dataSourceLabel = 'Select or input source indexes or index patterns'; - -const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel); - -const logTypeLabel = 'Select a log type you would like to detect'; - -const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel); - -const openDetectorDetails = (detectorName) => { - cy.getInputByPlaceholder('Search threat detectors') - .type(`${detectorName}`) - .pressEnterKey(); - cy.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; -}; - -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); - } - }); - }); -}; - -const editDetectorDetails = (detectorName, panelTitle) => { - cy.urlShouldContain('detector-details').then(() => { - cy.getElementByText('.euiTitle', detectorName); - cy.getElementByText('.euiPanel .euiTitle', panelTitle); - cy.getElementByText('.euiPanel .euiTitle', panelTitle) - .parent() - .siblings() - .within(() => cy.get('button').contains('Edit').click()); - }); -}; - -const validateAutomaticFieldMappingsPanel = (mappings) => - cy.get('.editFieldMappings').within(() => { - cy.get('.euiAccordion__triggerWrapper button').then(($btn) => { - cy.get($btn).contains(`Automatically mapped fields (${mappings.length})`); - - // 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.getElementByTestSubject('auto-mapped-fields-table') - .find('.euiBasicTable') - .validateTable(mappings); - }); - } - }); - }); - -const validatePendingFieldMappingsPanel = (mappings) => { - cy.get('.editFieldMappings').within(() => { - // Pending field mappings - cy.getElementByText('.euiTitle', 'Pending field mappings') - .parents('.euiPanel') - .within(() => { - cy.getElementByTestSubject('pending-mapped-fields-table') - .find('.euiBasicTable') - .validateTable(mappings); - }); - }); -}; - -const fillDetailsForm = (detectorName, dataSource) => { - getNameField().type(detectorName); - getDataSourceField().selectComboboxItem(dataSource); - getDataSourceField().blur(); - getLogTypeField().selectComboboxItem(cypressLogTypeDns); - getLogTypeField().blur(); -}; - -const createDetector = (detectorName, dataSource, expectFailure) => { - getCreateDetectorButton().click({ force: true }); - - fillDetailsForm(detectorName, dataSource); - - cy.getElementByText( - '.euiAccordion .euiTitle', - 'Detection rules (14 selected)' - ) - .click({ force: true, timeout: 5000 }) - .then(() => cy.contains('.euiTable .euiTableRow', 'Dns')); - - cy.getElementByText( - '.euiAccordion .euiTitle', - 'Configure 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(); - } - }); - - // go to the alerts page - getNextButton().click({ force: true }); - - // TEST ALERTS PAGE - cy.getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); - cy.getInputByPlaceholder('Enter a name to describe the alert condition').type( - 'test_trigger' - ); - cy.getElementByTestSubject('alert-tags-combo-box') - .type(`attack.defense_evasion{enter}`) - .find('input') - .focus() - .blur(); - - cy.getFieldByLabel('Specify alert severity').selectComboboxItem( - '1 (Highest)' - ); - - // go to review page - getNextButton().click({ force: true }); - - // TEST REVIEW AND CREATE PAGE - cy.getElementByText('.euiTitle', 'Review and create'); - cy.getElementByText('.euiTitle', 'Detector details'); - cy.getElementByText('.euiTitle', 'Field mapping'); - cy.getElementByText('.euiTitle', 'Alert triggers'); - - cy.validateDetailsItem('Detector name', detectorName); - cy.validateDetailsItem('Description', '-'); - cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); - cy.validateDetailsItem('Detection rules', '14'); - cy.validateDetailsItem('Created at', '-'); - cy.validateDetailsItem('Last updated time', '-'); - cy.validateDetailsItem( - 'Detector dashboard', - 'Not available for this log type' - ); - - validateAlertPanel('test_trigger'); - - cy.intercept('POST', NODE_API.MAPPINGS_BASE).as('createMappingsRequest'); - cy.intercept('POST', NODE_API.DETECTORS_BASE).as('createDetectorRequest'); - - // create the detector - cy.getElementByText('button', 'Create').click({ force: true }); - - // TEST DETECTOR DETAILS PAGE - cy.wait('@createMappingsRequest'); - - if (!expectFailure) { - cy.wait('@createDetectorRequest').then((interceptor) => { - const detectorId = interceptor.response.body.response._id; - - cy.url() - .should('contain', detectorId) - .then(() => { - cy.getElementByText( - '.euiCallOut', - `Detector created successfully: ${detectorName}` - ); - - // Confirm detector state - cy.getElementByText('.euiTitle', detectorName); - cy.getElementByText('.euiHealth', 'Active').then(() => { - cy.validateDetailsItem('Detector name', detectorName); - cy.validateDetailsItem('Description', '-'); - cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); - cy.validateDetailsItem('Detection rules', '14'); - cy.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.getElementByText('button.euiTab', 'Alert triggers') - .should('be.visible') - .click(); - validateAlertPanel('test_trigger'); - }); - }); - }); - } -}; - -const openCreateForm = () => getCreateDetectorButton().click({ force: true }); - -const getDescriptionField = () => - cy.getTextareaByLabel('Description - optional'); -const getTriggerNameField = () => cy.getFieldByLabel('Trigger name'); - -describe('Detectors', () => { - before(() => { - cy.cleanUpTests(); - - cy.createIndex(cypressIndexWindows, sample_windows_index_settings); - - // Create test index - cy.createIndex(cypressIndexDns, sample_dns_index_settings).then(() => - cy - .request( - 'POST', - '_plugins/_security_analytics/rules/_search?prePackaged=true', - { - from: 0, - size: 5000, - query: { - nested: { - path: 'rule', - query: { - bool: { must: [{ match: { 'rule.category': 'dns' } }] }, - }, - }, - }, - } - ) - .should('have.property', 'status', 200) - ); - - cy.createRule(dns_name_rule_data); - cy.createRule(dns_type_rule_data); - }); - - describe('...should validate form fields', () => { - beforeEach(() => { - cy.intercept(NODE_API.SEARCH_DETECTORS).as('detectorsSearch'); - - // Visit Detectors page before any test - cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); - cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); - - openCreateForm(); - }); - - 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'); - }); - - 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'); - }); - - it('...should validate data source field', () => { - getDataSourceField() - .focus() - .blur() - .parentsUntil('.euiFormRow__fieldWrapper') - .siblings() - .contains('Select an input source.'); - - getDataSourceField().selectComboboxItem(cypressIndexDns); - getDataSourceField() - .focus() - .blur() - .parentsUntil('.euiFormRow__fieldWrapper') - .find('.euiFormErrorText') - .should('not.exist'); - }); - - it('...should validate next button', () => { - getNextButton().should('be.disabled'); - - fillDetailsForm(detectorName, cypressIndexDns); - getNextButton().should('be.enabled'); - }); - - it('...should validate alerts page', () => { - fillDetailsForm(detectorName, cypressIndexDns); - getNextButton().click({ force: true }); - getTriggerNameField().should('be.empty'); - - getTriggerNameField().focus().blur(); - getTriggerNameField() - .parents('.euiFormRow__fieldWrapper') - .find('.euiFormErrorText') - .contains('Enter a name.'); - - getTriggerNameField().type('Trigger name').focus().blur(); - - getTriggerNameField() - .parents('.euiFormRow__fieldWrapper') - .find('.euiFormErrorText') - .should('not.exist'); - - getNextButton().should('be.enabled'); - - getTriggerNameField() - .type('{selectall}') - .type('{backspace}') - .focus() - .blur(); - getNextButton().should('be.disabled'); - - cy.getButtonByText('Remove').click({ force: true }); - getNextButton().should('be.enabled'); - }); - - it('...should show mappings warning', () => { - fillDetailsForm(detectorName, cypressIndexDns); - - getDataSourceField().selectComboboxItem(cypressIndexWindows); - getDataSourceField().focus().blur(); - - 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.' - ); - }); - }); - - describe('...validate create detector flow', () => { - beforeEach(() => { - cy.intercept(NODE_API.SEARCH_DETECTORS) - .as('detectorsSearch') - .as('detectorsSearch'); - - // Visit Detectors page before any test - cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`); - cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); - }); - - it('...can fail creation', () => { - createDetector(`${detectorName}_fail`, '.kibana_1', true); - cy.getElementByText('.euiCallOut', 'Create detector failed.'); - }); - - it('...can be created', () => { - createDetector(detectorName, cypressIndexDns, false); - cy.getElementByText('.euiCallOut', 'Detector created successfully'); - }); - - it('...basic details can be edited', () => { - cy.intercept('GET', NODE_API.INDICES_BASE).as('getIndices'); - openDetectorDetails(detectorName); - - editDetectorDetails(detectorName, 'Detector details'); - - cy.urlShouldContain('edit-detector-details').then(() => { - cy.getElementByText('.euiTitle', 'Edit detector details'); - }); - - cy.wait('@getIndices'); - getNameField() - .type('{selectall}{backspace}') - .type('test detector edited'); - cy.getTextareaByLabel('Description - optional').type( - 'Edited description' - ); - - getDataSourceField().clearCombobox(); - getDataSourceField().selectComboboxItem(cypressIndexWindows); - - cy.getFieldByLabel('Run every').type('{selectall}{backspace}').type('10'); - cy.getFieldByLabel('Run every', 'select').select('Hours'); - - cy.getElementByText('button', 'Save changes').click({ force: true }); - - cy.urlShouldContain('detector-details').then(() => { - cy.validateDetailsItem('Detector name', 'test detector edited'); - cy.validateDetailsItem('Description', 'Edited description'); - cy.validateDetailsItem('Detector schedule', 'Every 10 hours'); - cy.validateDetailsItem('Data source', cypressIndexWindows); - }); - }); - - it('...rules can be edited', () => { - openDetectorDetails(detectorName); - - editDetectorDetails(detectorName, 'Active rules'); - cy.getElementByText('.euiTitle', 'Detection rules (14)'); - - cy.getInputByPlaceholder('Search...') - .type(`${cypressDNSRule}`) - .pressEnterKey(); - - cy.getElementByText('.euiTableCellContent button', cypressDNSRule) - .parents('td') - .prev() - .find('.euiTableCellContent button') - .click(); - - cy.getElementByText('.euiTitle', 'Detection rules (13)'); - cy.getElementByText('button', 'Save changes').click({ force: true }); - cy.urlShouldContain('detector-details').then(() => { - cy.getElementByText('.euiTitle', detectorName); - cy.getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); - }); - }); - - it('...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); - - editDetectorDetails(detectorName, 'Detector details'); - - cy.urlShouldContain('edit-detector-details').then(() => { - cy.getElementByText('.euiTitle', 'Edit detector details'); - }); - - cy.wait('@getIndices'); - cy.get('.reviewFieldMappings').should('not.exist'); - - getDataSourceField().clearCombobox(); - getDataSourceField().should('not.have.value'); - getDataSourceField().type(`${cypressIndexDns}{enter}`); - - validateFieldMappingsTable('data source is changed'); - - cy.getElementByText('button', 'Save changes').click({ force: true }); - }); - - it('...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.urlShouldContain('edit-detector-rules').then(() => { - cy.getElementByText('.euiTitle', 'Edit detector rules'); - }); - - cy.get('.reviewFieldMappings').should('not.exist'); - - cy.wait('@detectorsSearch'); - - // 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 }); - - 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.getButtonByText('Actions') - .click({ force: true }) - .then(() => { - cy.intercept(NODE_API.DETECTORS_BASE).as('detectors'); - cy.getElementByText('.euiContextMenuItem', 'Delete').click({ - force: true, - }); - cy.wait('@detectors').then(() => { - cy.contains('There are no existing detectors'); - }); - }); - }); - }); - - after(() => cy.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 feb39989d..f90d88941 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 @@ -318,6 +318,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 }); @@ -366,7 +371,7 @@ describe('Alerts', () => { cy.get('tbody > tr') .filter(`:contains(${alertName})`) - .should('have.length', 3); + .should('have.length', 2); cy.get('tbody > tr') // Click the "Acknowledge" icon button in the first row @@ -377,11 +382,10 @@ describe('Alerts', () => { cy.get('tbody > tr') .filter(`:contains(${alertName})`) - .should('have.length', 2); - - cy.wait(10000); + .should('have.length', 1); // Filter the table to show only "Acknowledged" alerts + cy.get('[data-text="Status"]').click({ force: true }); cy.get('[class="euiFilterSelect__items"]').within(() => { cy.contains('Active').click({ force: true }); cy.contains('Acknowledged').click({ force: true }); @@ -390,7 +394,7 @@ describe('Alerts', () => { // Confirm there are now 3 "Acknowledged" alerts cy.get('tbody > tr') .filter(`:contains(${alertName})`) - .should('have.length', 2); + .should('have.length', 3); }); it('can be acknowledged via flyout button', () => { From df176011096a92971e4afa386c3535d343d92ffa Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Thu, 7 Sep 2023 11:16:05 -0700 Subject: [PATCH 3/9] dummy change to trigger tests Signed-off-by: Amardeepsingh Siglani --- .../security-analytics-dashboards-plugin/1_detectors.spec.js | 1 - 1 file changed, 1 deletion(-) 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 997b56ab7..c9c960b40 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 @@ -24,7 +24,6 @@ const getNameField = () => cy.getInputByPlaceholder('Enter a name for the detector.'); const getNextButton = () => cy.getButtonByText('Next'); - const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); const validateAlertPanel = (alertName) => From ddcdcaa76a8696048ccfff4f428000b8e87ee6b5 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Fri, 8 Sep 2023 13:18:14 -0700 Subject: [PATCH 4/9] Updated cypress commands to avoid duplication (#835) * updated commands to avoid duplication Signed-off-by: Amardeepsingh Siglani * updated command header for create rule; changed url for searching rules Signed-off-by: Amardeepsingh Siglani * fix linter issues Signed-off-by: Amardeepsingh Siglani --------- Signed-off-by: Amardeepsingh Siglani --- .../1_detectors.spec.js | 7 +- .../commands.js | 80 ++----------------- .../constants.js | 2 +- .../index.d.ts | 10 +-- 4 files changed, 16 insertions(+), 83 deletions(-) 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 c9c960b40..a0e4da177 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 @@ -12,6 +12,7 @@ import sample_dns_index_settings from '../../../fixtures/plugins/security-analyt 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 cypressIndexDns = 'cypress-index-dns'; const cypressIndexWindows = 'cypress-index-windows'; @@ -257,14 +258,14 @@ describe('Detectors', () => { before(() => { cy.cleanUpTests(); - cy.createIndex(cypressIndexWindows, sample_windows_index_settings); + cy.sa_createIndex(cypressIndexWindows, sample_windows_index_settings); // Create test index - cy.createIndex(cypressIndexDns, sample_dns_index_settings).then(() => + cy.sa_createIndex(cypressIndexDns, sample_dns_index_settings).then(() => cy .request( 'POST', - '_plugins/_security_analytics/rules/_search?prePackaged=true', + `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/_search?pre_packaged=true`, { from: 0, size: 5000, diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js index d0de8fcfc..9e49f0e20 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js @@ -36,42 +36,10 @@ const { BACKEND_BASE_PATH } = require('../../base_constants'); // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -// Be able to add default options to cy.request(), https://github.com/cypress-io/cypress/issues/726 -Cypress.Commands.overwrite('request', (originalFn, ...args) => { - let defaults = {}; - // Add the basic authentication header when security enabled in the Opensearch cluster - const ADMIN_AUTH = { - username: Cypress.env('username'), - password: Cypress.env('password'), - }; - if (Cypress.env('SECURITY_ENABLED')) { - defaults.auth = ADMIN_AUTH; - } - - let options = {}; - if (typeof args[0] === 'object' && args[0] !== null) { - options = Object.assign({}, args[0]); - } else if (args.length === 1) { - [options.url] = args; - } else if (args.length === 2) { - [options.method, options.url] = args; - } else if (args.length === 3) { - [options.method, options.url, options.body] = args; - } - - Object.assign(options, { - headers: { - 'osd-xsrf': '', - }, - }); - - return originalFn(Object.assign({}, defaults, options)); -}); - Cypress.Commands.add('cleanUpTests', () => { cy.deleteAllCustomRules(); cy.deleteAllDetectors(); - cy.deleteAllIndices(); + cy.sa_deleteAllIndices(); }); Cypress.Commands.add('getTableFirstRow', (selector) => { @@ -378,7 +346,7 @@ Cypress.Commands.add( } ); -Cypress.Commands.add('createIndex', (index, settings = {}) => { +Cypress.Commands.add('sa_createIndex', (index, settings = {}) => { cy.request('PUT', `${BACKEND_BASE_PATH}/${index}`, settings).should( 'have.property', 'status', @@ -386,14 +354,6 @@ Cypress.Commands.add('createIndex', (index, settings = {}) => { ); }); -Cypress.Commands.add('createIndexTemplate', (name, template) => { - cy.request( - 'PUT', - `${BACKEND_BASE_PATH}${NODE_API.INDEX_TEMPLATE_BASE}/${name}`, - template - ); -}); - Cypress.Commands.add('ingestDocument', (indexId, documentJSON) => { cy.request('POST', `${BACKEND_BASE_PATH}/${indexId}/_doc`, documentJSON); }); @@ -409,16 +369,7 @@ Cypress.Commands.add( } ); -Cypress.Commands.add('deleteIndex', (indexName, options = {}) => { - cy.request({ - method: 'DELETE', - url: `${BACKEND_BASE_PATH}/${indexName}`, - failOnStatusCode: false, - ...options, - }); -}); - -Cypress.Commands.add('deleteAllIndices', () => { +Cypress.Commands.add('sa_deleteAllIndices', () => { cy.request({ method: 'DELETE', url: `${BACKEND_BASE_PATH}/index*,sample*,opensearch_dashboards*,test*,cypress*`, @@ -435,6 +386,9 @@ Cypress.Commands.add('createRule', (ruleJSON) => { method: 'POST', url: `${OPENSEARCH_DASHBOARDS}${NODE_API.RULES_BASE}?category=${ruleJSON.category}`, body: JSON.stringify(ruleJSON), + headers: { + 'osd-xsrf': true, + }, }); }); @@ -526,25 +480,3 @@ Cypress.Commands.add( return cy.get(subject).wait(10).focus().realType(text); } ); - -Cypress.Commands.add( - '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; - } -); diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js index a0f510043..3bdd9d238 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js @@ -82,7 +82,7 @@ export const createDetector = ( cy.cleanUpTests() // Create test index - .then(() => cy.createIndex(indexName, indexSettings)) + .then(() => cy.sa_createIndex(indexName, indexSettings)) // Create field mappings .then(() => diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts index 1225c9c59..c4805c8cc 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts @@ -205,11 +205,11 @@ declare namespace Cypress { ospType(text: string): Chainable; /** - * Creates index with policy + * Creates index with optional settings * @example - * cy.createIndex("some_index", "some_policy") + * cy.sa_createIndex("some_index", settingObj) */ - createIndex(index: string, settings?: object): Chainable; + sa_createIndex(index: string, settings?: object): Chainable; /** * Creates an index template. @@ -222,9 +222,9 @@ declare namespace Cypress { /** * Deletes all indices in cluster * @example - * cy.deleteAllIndices() + * cy.sa_deleteAllIndices() */ - deleteAllIndices(): Chainable; + sa_deleteAllIndices(): Chainable; /** * Deletes all custom rules in cluster From 8757784625b8c5e9164a0e3f21eb004d25afdea5 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 13 Sep 2023 12:43:18 -0700 Subject: [PATCH 5/9] updated mappings for test index (#841) Signed-off-by: Amardeepsingh Siglani --- .../detector/create_usb_detector_mappings_data.json | 4 ++-- .../integration_tests/index/add_windows_index_data.json | 2 +- .../integration_tests/index/create_windows_settings.json | 2 +- .../integration_tests/rule/create_windows_usb_rule.json | 2 +- .../sample_alias_mappings.json | 4 ++-- .../sample_document.json | 2 +- .../sample_field_mappings.json | 4 ++-- .../sample_windows_index_settings.json | 2 +- .../security-analytics-dashboards-plugin/3_alerts.spec.js | 6 +----- .../security-analytics-dashboards-plugin/constants.js | 2 ++ 10 files changed, 14 insertions(+), 16 deletions(-) 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 0cad430bc..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,8 +1,8 @@ { "properties": { - "winlog-event_id": { + "winlog.event_id": { "type": "alias", - "path": "winlog.event_id" + "path": "EventID" }, "winlog-provider_name": { "type": "alias", 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 f8b8b4e2e..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,3 +1,3 @@ { - "winlog.event_id": "2003" + "EventID": "2003" } 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 480f63ba1..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,7 +1,7 @@ { "mappings": { "properties": { - "winlog.event_id": { + "EventID": { "type": "integer" }, "winlog.provider_name": { 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 fb14944c6..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 @@ -16,7 +16,7 @@ } ], "log_source": "", - "detection": "selection:\n winlog-event_id:\n - 2003\n - 2100\n - 2102\ncondition: selection", + "detection": "selection:\n EventID:\n - 2003\n - 2100\n - 2102\ncondition: selection", "level": "high", "false_positives": [ { 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 e0a1a5f88..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,8 +1,8 @@ { "properties": { - "winlog-event_id": { + "winlog.event_id": { "type": "alias", - "path": "winlog.event_id" + "path": "EventID" } } } 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 521d2f677..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,3 +1,3 @@ { - "winlog.event_id": 2003 + "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 ff4eb1830..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,7 +1,7 @@ { "properties": { - "winlog-event_id": { - "path": "winlog.event_id", + "winlog.event_id": { + "path": "EventID", "type": "alias" } } 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 index 480f63ba1..02c187caf 100644 --- 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 @@ -1,7 +1,7 @@ { "mappings": { "properties": { - "winlog.event_id": { + "EventID": { "type": "integer" }, "winlog.provider_name": { 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 f90d88941..d2b32f882 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 @@ -259,11 +259,7 @@ describe('Alerts', () => { // 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('{"winlog.event_id": 2003}'), - 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"]') diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js index 3bdd9d238..d7b7595c9 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js @@ -117,6 +117,8 @@ export const createDetector = ( }); }); + // 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.insertDocumentToIndex(indexName, '', indexDoc); From 982ddfbff44d67d1c6a4dac23891da5a56b5c323 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Thu, 14 Sep 2023 09:52:55 -0700 Subject: [PATCH 6/9] avoid clicking filter menu btn twice (#849) Signed-off-by: Amardeepsingh Siglani --- .../security-analytics-dashboards-plugin/3_alerts.spec.js | 1 - 1 file changed, 1 deletion(-) 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 d2b32f882..f84b86cab 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 @@ -381,7 +381,6 @@ describe('Alerts', () => { .should('have.length', 1); // Filter the table to show only "Acknowledged" alerts - cy.get('[data-text="Status"]').click({ force: true }); cy.get('[class="euiFilterSelect__items"]').within(() => { cy.contains('Active').click({ force: true }); cy.contains('Acknowledged').click({ force: true }); From 5c1ba7ad85d8a318f984e8eb2d588124c5696b8b Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Fri, 15 Sep 2023 12:07:23 -0700 Subject: [PATCH 7/9] removed check for url since it differs with and without security (#863) Signed-off-by: Amardeepsingh Siglani --- .../security-analytics-dashboards-plugin/commands.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js index 9e49f0e20..989590f8b 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js @@ -54,11 +54,9 @@ Cypress.Commands.add( Cypress.log({ message: `Wait for url: ${fullUrl} to be loaded.`, }); - cy.url({ timeout: timeout }) - .should('include', fullUrl) - .then(() => { - contains && cy.contains(contains).should('be.visible'); - }); + cy.url({ timeout: timeout }).then(() => { + contains && cy.contains(contains).should('be.visible'); + }); } ); From c72a7b7b6506c3410203f844424d5d5ade5a9a78 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Thu, 5 Oct 2023 03:09:47 -0700 Subject: [PATCH 8/9] namespaced all commands; updated tests for 2.11 Signed-off-by: Amardeepsingh Siglani --- .../1_detectors.spec.js | 209 ++++++++---------- .../2_rules.spec.js | 123 ++++++----- .../3_alerts.spec.js | 6 +- .../4_findings.spec.js | 32 +-- .../commands.js | 93 ++++---- .../constants.js | 10 +- .../index.d.ts | 140 ++++++------ 7 files changed, 299 insertions(+), 314 deletions(-) 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 a0e4da177..889460ec4 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 @@ -22,32 +22,32 @@ const cypressLogTypeDns = 'dns'; const cypressDNSRule = dns_name_rule_data.title; const getNameField = () => - cy.getInputByPlaceholder('Enter a name for the detector.'); + cy.sa_getInputByPlaceholder('Enter a name for the detector.'); -const getNextButton = () => cy.getButtonByText('Next'); -const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); +const getNextButton = () => cy.sa_getButtonByText('Next'); +const getCreateDetectorButton = () => cy.sa_getButtonByText('Create detector'); const validateAlertPanel = (alertName) => cy - .getElementByText('.euiTitle', 'Alert triggers') + .sa_getElementByText('.euiTitle', 'Alert triggers') .parentsUntil('.euiPanel') .siblings() .eq(2) - .within(() => cy.getElementByText('button', alertName)); + .within(() => cy.sa_getElementByText('button', alertName)); const dataSourceLabel = 'Select or input source indexes or index patterns'; -const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel); +const getDataSourceField = () => cy.sa_getFieldByLabel(dataSourceLabel); const logTypeLabel = 'Select a log type you would like to detect'; -const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel); +const getLogTypeField = () => cy.sa_getFieldByLabel(logTypeLabel); const openDetectorDetails = (detectorName) => { - cy.getInputByPlaceholder('Search threat detectors') + cy.sa_getInputByPlaceholder('Search threat detectors') .type(`${detectorName}`) - .pressEnterKey(); - cy.getElementByText('.euiTableCellContent button', detectorName).click(); + .sa_pressEnterKey(); + cy.sa_getElementByText('.euiTableCellContent button', detectorName).click(); }; const getMappingFields = (properties, items = [], prefix = '') => { @@ -92,10 +92,10 @@ const validateFieldMappingsTable = (message = '') => { }; const editDetectorDetails = (detectorName, panelTitle) => { - cy.urlShouldContain('detector-details').then(() => { - cy.getElementByText('.euiTitle', detectorName); - cy.getElementByText('.euiPanel .euiTitle', panelTitle); - cy.getElementByText('.euiPanel .euiTitle', 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()); @@ -112,9 +112,9 @@ const validateAutomaticFieldMappingsPanel = (mappings) => cy.get($btn[0]) .click() .then(() => { - cy.getElementByTestSubject('auto-mapped-fields-table') + cy.sa_getElementByTestSubject('auto-mapped-fields-table') .find('.euiBasicTable') - .validateTable(mappings); + .sa_validateTable(mappings); }); } }); @@ -123,21 +123,21 @@ const validateAutomaticFieldMappingsPanel = (mappings) => const validatePendingFieldMappingsPanel = (mappings) => { cy.get('.editFieldMappings').within(() => { // Pending field mappings - cy.getElementByText('.euiTitle', 'Pending field mappings') + cy.sa_getElementByText('.euiTitle', 'Pending field mappings') .parents('.euiPanel') .within(() => { - cy.getElementByTestSubject('pending-mapped-fields-table') + cy.sa_getElementByTestSubject('pending-mapped-fields-table') .find('.euiBasicTable') - .validateTable(mappings); + .sa_validateTable(mappings); }); }); }; const fillDetailsForm = (detectorName, dataSource) => { getNameField().type(detectorName); - getDataSourceField().selectComboboxItem(dataSource); + getDataSourceField().sa_selectComboboxItem(dataSource); getDataSourceField().blur(); - getLogTypeField().selectComboboxItem(cypressLogTypeDns); + getLogTypeField().sa_selectComboboxItem(cypressLogTypeDns); getLogTypeField().blur(); }; @@ -146,17 +146,14 @@ const createDetector = (detectorName, dataSource, expectFailure) => { fillDetailsForm(detectorName, dataSource); - cy.getElementByText( + cy.sa_getElementByText( '.euiAccordion .euiTitle', 'Detection rules (14 selected)' ) .click({ force: true, timeout: 5000 }) .then(() => cy.contains('.euiTable .euiTableRow', 'Dns')); - cy.getElementByText( - '.euiAccordion .euiTitle', - 'Configure field mapping - optional' - ); + 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') { @@ -168,47 +165,27 @@ const createDetector = (detectorName, dataSource, expectFailure) => { getNextButton().click({ force: true }); // TEST ALERTS PAGE - cy.getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers'); - cy.getInputByPlaceholder('Enter a name to describe the alert condition').type( - 'test_trigger' - ); - cy.getElementByTestSubject('alert-tags-combo-box') + // 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(); - cy.getFieldByLabel('Specify alert severity').selectComboboxItem( + cy.sa_getFieldByLabel('Specify alert severity').sa_selectComboboxItem( '1 (Highest)' ); - // go to review page - getNextButton().click({ force: true }); - - // TEST REVIEW AND CREATE PAGE - cy.getElementByText('.euiTitle', 'Review and create'); - cy.getElementByText('.euiTitle', 'Detector details'); - cy.getElementByText('.euiTitle', 'Field mapping'); - cy.getElementByText('.euiTitle', 'Alert triggers'); - - cy.validateDetailsItem('Detector name', detectorName); - cy.validateDetailsItem('Description', '-'); - cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); - cy.validateDetailsItem('Detection rules', '14'); - cy.validateDetailsItem('Created at', '-'); - cy.validateDetailsItem('Last updated time', '-'); - cy.validateDetailsItem( - 'Detector dashboard', - 'Not available for this log type' - ); - - validateAlertPanel('test_trigger'); - cy.intercept('POST', NODE_API.MAPPINGS_BASE).as('createMappingsRequest'); cy.intercept('POST', NODE_API.DETECTORS_BASE).as('createDetectorRequest'); // create the detector - cy.getElementByText('button', 'Create').click({ force: true }); + cy.sa_getElementByText('button', 'Create').click({ force: true }); // TEST DETECTOR DETAILS PAGE cy.wait('@createMappingsRequest'); @@ -220,28 +197,28 @@ const createDetector = (detectorName, dataSource, expectFailure) => { cy.url() .should('contain', detectorId) .then(() => { - cy.getElementByText( + cy.sa_getElementByText( '.euiCallOut', `Detector created successfully: ${detectorName}` ); // Confirm detector state - cy.getElementByText('.euiTitle', detectorName); - cy.getElementByText('.euiHealth', 'Active').then(() => { - cy.validateDetailsItem('Detector name', detectorName); - cy.validateDetailsItem('Description', '-'); - cy.validateDetailsItem('Detector schedule', 'Every 1 minute'); - cy.validateDetailsItem('Detection rules', '14'); - cy.validateDetailsItem( + 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.getElementByText('button.euiTab', 'Alert triggers') + cy.sa_getElementByText('button.euiTab', 'Alert triggers') .should('be.visible') .click(); - validateAlertPanel('test_trigger'); + validateAlertPanel('Trigger 1'); }); }); }); @@ -251,12 +228,12 @@ const createDetector = (detectorName, dataSource, expectFailure) => { const openCreateForm = () => getCreateDetectorButton().click({ force: true }); const getDescriptionField = () => - cy.getTextareaByLabel('Description - optional'); -const getTriggerNameField = () => cy.getFieldByLabel('Trigger name'); + cy.sa_getTextareaByLabel('Description - optional'); +const getTriggerNameField = () => cy.sa_getFieldByLabel('Trigger name'); describe('Detectors', () => { before(() => { - cy.cleanUpTests(); + cy.sa_cleanUpTests(); cy.sa_createIndex(cypressIndexWindows, sample_windows_index_settings); @@ -282,8 +259,8 @@ describe('Detectors', () => { .should('have.property', 'status', 200) ); - cy.createRule(dns_name_rule_data); - cy.createRule(dns_type_rule_data); + cy.sa_createRule(dns_name_rule_data); + cy.sa_createRule(dns_type_rule_data); }); describe('...should validate form fields', () => { @@ -380,7 +357,7 @@ describe('Detectors', () => { .siblings() .contains('Select an input source.'); - getDataSourceField().selectComboboxItem(cypressIndexDns); + getDataSourceField().sa_selectComboboxItem(cypressIndexDns); getDataSourceField() .focus() .blur() @@ -399,38 +376,32 @@ describe('Detectors', () => { it('...should validate alerts page', () => { fillDetailsForm(detectorName, cypressIndexDns); getNextButton().click({ force: true }); - getTriggerNameField().should('be.empty'); - - getTriggerNameField().focus().blur(); - getTriggerNameField() - .parents('.euiFormRow__fieldWrapper') - .find('.euiFormErrorText') - .contains('Enter a name.'); - - getTriggerNameField().type('Trigger name').focus().blur(); + // 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'); - getNextButton().should('be.enabled'); + getCreateDetectorButton().should('be.enabled'); getTriggerNameField() .type('{selectall}') .type('{backspace}') .focus() .blur(); - getNextButton().should('be.disabled'); + getCreateDetectorButton().should('be.disabled'); - cy.getButtonByText('Remove').click({ force: true }); - getNextButton().should('be.enabled'); + cy.sa_getButtonByText('Remove').click({ force: true }); + getCreateDetectorButton().should('be.enabled'); }); it('...should show mappings warning', () => { fillDetailsForm(detectorName, cypressIndexDns); - getDataSourceField().selectComboboxItem(cypressIndexWindows); + getDataSourceField().sa_selectComboboxItem(cypressIndexWindows); getDataSourceField().focus().blur(); cy.get('[data-test-subj="define-detector-diff-log-types-warning"]') @@ -454,12 +425,12 @@ describe('Detectors', () => { it('...can fail creation', () => { createDetector(`${detectorName}_fail`, '.kibana_1', true); - cy.getElementByText('.euiCallOut', 'Create detector failed.'); + cy.sa_getElementByText('.euiCallOut', 'Create detector failed.'); }); it('...can be created', () => { createDetector(detectorName, cypressIndexDns, false); - cy.getElementByText('.euiCallOut', 'Detector created successfully'); + cy.sa_getElementByText('.euiCallOut', 'Detector created successfully'); }); it('...basic details can be edited', () => { @@ -468,31 +439,33 @@ describe('Detectors', () => { editDetectorDetails(detectorName, 'Detector details'); - cy.urlShouldContain('edit-detector-details').then(() => { - cy.getElementByText('.euiTitle', 'Edit detector details'); + cy.sa_urlShouldContain('edit-detector-details').then(() => { + cy.sa_getElementByText('.euiTitle', 'Edit detector details'); }); cy.wait('@getIndices'); getNameField() .type('{selectall}{backspace}') .type('test detector edited'); - cy.getTextareaByLabel('Description - optional').type( + cy.sa_getTextareaByLabel('Description - optional').type( 'Edited description' ); - getDataSourceField().clearCombobox(); - getDataSourceField().selectComboboxItem(cypressIndexWindows); + getDataSourceField().sa_clearCombobox(); + getDataSourceField().sa_selectComboboxItem(cypressIndexWindows); - cy.getFieldByLabel('Run every').type('{selectall}{backspace}').type('10'); - cy.getFieldByLabel('Run every', 'select').select('Hours'); + cy.sa_getFieldByLabel('Run every') + .type('{selectall}{backspace}') + .type('10'); + cy.sa_getFieldByLabel('Run every', 'select').select('Hours'); - cy.getElementByText('button', 'Save changes').click({ force: true }); + cy.sa_getElementByText('button', 'Save changes').click({ force: true }); - cy.urlShouldContain('detector-details').then(() => { - cy.validateDetailsItem('Detector name', 'test detector edited'); - cy.validateDetailsItem('Description', 'Edited description'); - cy.validateDetailsItem('Detector schedule', 'Every 10 hours'); - cy.validateDetailsItem('Data source', cypressIndexWindows); + 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); }); }); @@ -500,23 +473,23 @@ describe('Detectors', () => { openDetectorDetails(detectorName); editDetectorDetails(detectorName, 'Active rules'); - cy.getElementByText('.euiTitle', 'Detection rules (14)'); + cy.sa_getElementByText('.euiTitle', 'Detection rules (14)'); - cy.getInputByPlaceholder('Search...') + cy.sa_getInputByPlaceholder('Search...') .type(`${cypressDNSRule}`) - .pressEnterKey(); + .sa_pressEnterKey(); - cy.getElementByText('.euiTableCellContent button', cypressDNSRule) + cy.sa_getElementByText('.euiTableCellContent button', cypressDNSRule) .parents('td') .prev() .find('.euiTableCellContent button') .click(); - cy.getElementByText('.euiTitle', 'Detection rules (13)'); - cy.getElementByText('button', 'Save changes').click({ force: true }); - cy.urlShouldContain('detector-details').then(() => { - cy.getElementByText('.euiTitle', detectorName); - cy.getElementByText('.euiPanel .euiTitle', 'Active rules (13)'); + 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)'); }); }); @@ -529,20 +502,20 @@ describe('Detectors', () => { editDetectorDetails(detectorName, 'Detector details'); - cy.urlShouldContain('edit-detector-details').then(() => { - cy.getElementByText('.euiTitle', 'Edit detector details'); + cy.sa_urlShouldContain('edit-detector-details').then(() => { + cy.sa_getElementByText('.euiTitle', 'Edit detector details'); }); cy.wait('@getIndices'); cy.get('.reviewFieldMappings').should('not.exist'); - getDataSourceField().clearCombobox(); + getDataSourceField().sa_clearCombobox(); getDataSourceField().should('not.have.value'); getDataSourceField().type(`${cypressIndexDns}{enter}`); validateFieldMappingsTable('data source is changed'); - cy.getElementByText('button', 'Save changes').click({ force: true }); + cy.sa_getElementByText('button', 'Save changes').click({ force: true }); }); xit('...should show field mappings if rule selection is changed', () => { @@ -554,8 +527,8 @@ describe('Detectors', () => { editDetectorDetails(detectorName, 'Active rules'); - cy.urlShouldContain('edit-detector-rules').then(() => { - cy.getElementByText('.euiTitle', 'Edit detector rules'); + cy.sa_urlShouldContain('edit-detector-rules').then(() => { + cy.sa_getElementByText('.euiTitle', 'Edit detector rules'); }); cy.get('.reviewFieldMappings').should('not.exist'); @@ -583,11 +556,11 @@ describe('Detectors', () => { cy.wait('@getCustomRules'); cy.wait('@getSigmaRules'); - cy.getButtonByText('Actions') + cy.sa_getButtonByText('Actions') .click({ force: true }) .then(() => { cy.intercept(`${NODE_API.DETECTORS_BASE}/_search`).as('detectors'); - cy.getElementByText('.euiContextMenuItem', 'Delete').click({ + cy.sa_getElementByText('.euiContextMenuItem', 'Delete').click({ force: true, }); cy.wait('@detectors').then(() => { @@ -597,5 +570,5 @@ describe('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 ac9998122..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 @@ -19,7 +19,7 @@ const SAMPLE_RULE = { 'FieldKey|contains:', '- FieldValue', ], - severity: 'critical', + severity: 'Critical', tags: [ 'attack.persistence', 'attack.privilege_escalation', @@ -43,7 +43,7 @@ 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}'`, @@ -54,7 +54,7 @@ const YAML_RULE_LINES = [ 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({ @@ -90,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 @@ -151,12 +151,13 @@ const checkRulesFlyout = () => { }; const getCreateButton = () => cy.get('[data-test-subj="create_rule_button"]'); -const getNameField = () => cy.getFieldByLabel('Rule name'); -const getRuleStatusField = () => cy.getFieldByLabel('Rule Status'); -const getDescriptionField = () => cy.getFieldByLabel('Description - optional'); -const getAuthorField = () => cy.getFieldByLabel('Author'); -const getLogTypeField = () => cy.getFieldByLabel('Log type'); -const getRuleLevelField = () => cy.getFieldByLabel('Rule level (severity)'); +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"]'); @@ -195,8 +196,8 @@ const fillCreateForm = () => { getAuthorField().type(`${SAMPLE_RULE.author}`); // rule details - getLogTypeField().type(SAMPLE_RULE.logType); - getRuleLevelField().selectComboboxItem(SAMPLE_RULE.severity); + getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType); + getRuleLevelField().sa_selectComboboxItem(SAMPLE_RULE.severity); // rule detection getSelectionPanelByIndex(0).within(() => { @@ -213,7 +214,7 @@ const fillCreateForm = () => { SAMPLE_RULE.tags.forEach((tag, idx) => { getTagField(idx).type(tag); idx < SAMPLE_RULE.tags.length - 1 && - cy.getButtonByText('Add tag').click({ force: true }); + cy.sa_getButtonByText('Add tag').click({ force: true }); }); getReferenceFieldByIndex(0).type(SAMPLE_RULE.references); @@ -221,7 +222,7 @@ const fillCreateForm = () => { }; describe('Rules', () => { - before(() => cy.cleanUpTests()); + before(() => cy.sa_cleanUpTests()); describe('...should validate form fields', () => { beforeEach(() => { @@ -233,7 +234,7 @@ describe('Rules', () => { cy.wait('@rulesSearch').should('have.property', 'state', 'Complete'); // Check that correct page is showing - cy.waitForPageLoad('rules', { + cy.sa_waitForPageLoad('rules', { contains: 'Detection rules', }); @@ -241,15 +242,15 @@ describe('Rules', () => { }); it('...should validate rule name', () => { - getNameField().containsHelperText( + getNameField().sa_containsHelperText( 'Rule name must contain 5-50 characters. Valid characters are a-z, A-Z, 0-9, hyphens, spaces, and underscores' ); getNameField().should('be.empty'); getNameField().focus().blur(); - getNameField().containsError('Rule name is required'); + getNameField().sa_containsError('Rule name is required'); getNameField().type('text').focus().blur(); - getNameField().containsError('Invalid rule name.'); + getNameField().sa_containsError('Invalid rule name.'); getNameField() .type('{selectall}') @@ -257,7 +258,7 @@ describe('Rules', () => { .type('tex&') .focus() .blur(); - getNameField().containsError('Invalid rule name.'); + getNameField().sa_containsError('Invalid rule name.'); getNameField() .type('{selectall}') @@ -265,7 +266,7 @@ describe('Rules', () => { .type('Rule name') .focus() .blur() - .shouldNotHaveError(); + .sa_shouldNotHaveError(); }); it('...should validate rule description field', () => { @@ -301,15 +302,15 @@ describe('Rules', () => { }); it('...should validate author', () => { - getAuthorField().containsHelperText( + getAuthorField().sa_containsHelperText( 'Combine multiple authors separated with a comma' ); getAuthorField().should('be.empty'); getAuthorField().focus().blur(); - getAuthorField().containsError('Author name is required'); + getAuthorField().sa_containsError('Author name is required'); getAuthorField().type('text').focus().blur(); - getAuthorField().containsError('Invalid author.'); + getAuthorField().sa_containsError('Invalid author.'); getAuthorField() .type('{selectall}') @@ -317,7 +318,7 @@ describe('Rules', () => { .type('tex&') .focus() .blur(); - getAuthorField().containsError('Invalid author.'); + getAuthorField().sa_containsError('Invalid author.'); getAuthorField() .type('{selectall}') @@ -325,40 +326,40 @@ describe('Rules', () => { .type('Rule name') .focus() .blur() - .shouldNotHaveError(); + .sa_shouldNotHaveError(); }); it('...should validate log type field', () => { getLogTypeField().should('be.empty'); getLogTypeField().focus().blur(); - getLogTypeField().containsError('Log type is required'); + getLogTypeField().sa_containsError('Log type is required'); - getLogTypeField().selectComboboxItem(SAMPLE_RULE.logType); - getLogTypeField().focus().blur().shouldNotHaveError(); + getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType); + getLogTypeField().focus().blur().sa_shouldNotHaveError(); }); it('...should validate rule level field', () => { getRuleLevelField().should('be.empty'); getRuleLevelField().focus().blur(); - getRuleLevelField().containsError('Rule level is required'); + getRuleLevelField().sa_containsError('Rule level is required'); - getRuleLevelField().selectComboboxItem(SAMPLE_RULE.severity); - getRuleLevelField().focus().blur().shouldNotHaveError(); + getRuleLevelField().sa_selectComboboxItem(SAMPLE_RULE.severity); + getRuleLevelField().focus().blur().sa_shouldNotHaveError(); }); it('...should validate rule status field', () => { - getRuleStatusField().containsValue(SAMPLE_RULE.status); - getRuleStatusField().focus().blur().shouldNotHaveError(); + getRuleStatusField().sa_containsValue(SAMPLE_RULE.status); + getRuleStatusField().focus().blur().sa_shouldNotHaveError(); - getRuleStatusField().clearCombobox(); + getRuleStatusField().sa_clearCombobox(); getRuleStatusField().focus().blur(); - getRuleStatusField().containsError('Rule status is required'); + getRuleStatusField().sa_containsError('Rule status is required'); }); it('...should validate selection', () => { getSelectionPanelByIndex(0).within(() => { getSelectionNameField().should('have.value', 'Selection_1'); - getSelectionNameField().clearValue(); + getSelectionNameField().sa_clearValue(); getSelectionNameField().focus().blur(); getSelectionNameField() .parentsUntil('.euiFormRow__fieldWrapper') @@ -456,7 +457,7 @@ describe('Rules', () => { .parents('.euiFormRow__fieldWrapper') .contains("Tags must start with 'attack.'"); - getTagField(0).clearValue().type('attack.tag'); + getTagField(0).sa_clearValue().type('attack.tag'); getTagField(0) .parents('.euiFormRow__fieldWrapper') .find('.euiFormErrorText') @@ -468,29 +469,29 @@ describe('Rules', () => { fillCreateForm(); // rule name field - getNameField().clearValue(); + getNameField().sa_clearValue(); toastShouldExist(); getNameField().type('Rule name'); // author field - getAuthorField().clearValue(); + getAuthorField().sa_clearValue(); toastShouldExist(); getAuthorField().type('John Doe'); // log field - getLogTypeField().clearCombobox(); + getLogTypeField().sa_clearCombobox(); toastShouldExist(); - getLogTypeField().selectComboboxItem(SAMPLE_RULE.logType); + getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType); // severity field - getRuleLevelField().clearCombobox(); + getRuleLevelField().sa_clearCombobox(); toastShouldExist(); - getRuleLevelField().selectComboboxItem(SAMPLE_RULE.severity); + getRuleLevelField().sa_selectComboboxItem(SAMPLE_RULE.severity); // status field - getRuleStatusField().clearCombobox(); + getRuleStatusField().sa_clearCombobox(); toastShouldExist(); - getRuleStatusField().selectComboboxItem(SAMPLE_RULE.status); + getRuleStatusField().sa_selectComboboxItem(SAMPLE_RULE.status); // selection name field getSelectionPanelByIndex(0).within(() => @@ -522,7 +523,7 @@ describe('Rules', () => { // selection map list field getSelectionPanelByIndex(0).within(() => { getListRadioField().click({ force: true }); - getMapListField().clearValue(); + getMapListField().sa_clearValue(); }); toastShouldExist(); getSelectionPanelByIndex(0).within(() => { @@ -536,9 +537,9 @@ describe('Rules', () => { getConditionAddButton().click({ force: true }); // tags field - getTagField(0).clearValue().type('wrong.tag'); + getTagField(0).sa_clearValue().type('wrong.tag'); toastShouldExist(); - getTagField(0).clearValue().type('attack.tag'); + getTagField(0).sa_clearValue().type('attack.tag'); }); }); @@ -552,7 +553,7 @@ describe('Rules', () => { cy.wait('@rulesSearch').should('have.property', 'state', 'Complete'); // Check that correct page is showing - cy.waitForPageLoad('rules', { + cy.sa_waitForPageLoad('rules', { contains: 'Detection rules', }); }); @@ -579,7 +580,7 @@ describe('Rules', () => { cy.wait('@getRules'); - cy.waitForPageLoad('rules', { + cy.sa_waitForPageLoad('rules', { contains: 'Detection rules', }); @@ -587,11 +588,13 @@ describe('Rules', () => { }); it('...can be edited', () => { - cy.waitForPageLoad('rules', { + cy.sa_waitForPageLoad('rules', { contains: 'Detection rules', }); - cy.get(`input[placeholder="Search rules"]`).ospSearch(SAMPLE_RULE.name); + cy.get(`input[placeholder="Search rules"]`).sa_ospSearch( + SAMPLE_RULE.name + ); cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`).click({ force: true, }); @@ -611,13 +614,13 @@ describe('Rules', () => { getNameField().type(SAMPLE_RULE.name); getNameField().should('have.value', SAMPLE_RULE.name); - getLogTypeField().clearCombobox(); + getLogTypeField().sa_clearCombobox(); SAMPLE_RULE.logType = 'dns'; YAML_RULE_LINES[2] = `product: ${SAMPLE_RULE.logType}`; YAML_RULE_LINES[3] = `title: ${SAMPLE_RULE.name}`; - getLogTypeField().type(SAMPLE_RULE.logType).type('{enter}'); + getLogTypeField().sa_selectComboboxItem(SAMPLE_RULE.logType); getLogTypeField() - .containsValue(SAMPLE_RULE.logType) + .sa_containsValue(SAMPLE_RULE.logType) .contains(SAMPLE_RULE.logType); SAMPLE_RULE.description += ' edited'; @@ -632,7 +635,7 @@ describe('Rules', () => { submitRule(); - cy.waitForPageLoad('rules', { + cy.sa_waitForPageLoad('rules', { contains: 'Detection rules', }); @@ -650,7 +653,9 @@ describe('Rules', () => { delay: 5000, }).as('getCustomRules'); - 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({ @@ -677,7 +682,7 @@ describe('Rules', () => { // Search for sample_detector, presumably deleted cy.wait(3000); - cy.get(`input[placeholder="Search rules"]`).ospSearch( + cy.get(`input[placeholder="Search rules"]`).sa_ospSearch( SAMPLE_RULE.name ); // Click the rule link to open the details flyout @@ -686,5 +691,5 @@ describe('Rules', () => { }); }); - 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 f84b86cab..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 @@ -55,7 +55,7 @@ describe('Alerts', () => { cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete'); // Wait for page to load - cy.waitForPageLoad('alerts', { + cy.sa_waitForPageLoad('alerts', { contains: 'Security alerts', }); @@ -145,7 +145,7 @@ describe('Alerts', () => { expect($tr, `timestamp`).to.contain(date); expect($tr, `rule name`).to.contain('Cypress USB Rule'); expect($tr, `detector name`).to.contain(testDetectorCfg.name); - expect($tr, `log type`).to.contain('windows'); + expect($tr, `log type`).to.contain('System Activity: Windows'); }); // Close the flyout @@ -466,5 +466,5 @@ describe('Alerts', () => { ); }); - 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 462c83fa2..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 @@ -40,7 +40,7 @@ describe('Findings', () => { cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/findings`); // Wait for page to load - cy.waitForPageLoad('findings', { + cy.sa_waitForPageLoad('findings', { contains: 'Findings', }); @@ -55,18 +55,20 @@ describe('Findings', () => { cy.contains('No items found').should('not.exist'); // Check for expected findings - cy.contains('windows'); + 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(indexName); + 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'); @@ -80,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(indexName); + 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 }); @@ -101,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(indexName); + 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 }); @@ -128,12 +130,14 @@ 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(indexName); + 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( @@ -148,5 +152,5 @@ describe('Findings', () => { }); }); - 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 989590f8b..708b1e8bc 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/commands.js @@ -36,19 +36,19 @@ const { BACKEND_BASE_PATH } = require('../../base_constants'); // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -Cypress.Commands.add('cleanUpTests', () => { - cy.deleteAllCustomRules(); - cy.deleteAllDetectors(); +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( - 'waitForPageLoad', + 'sa_waitForPageLoad', (pathname, { timeout = 60000, contains = null }) => { const fullUrl = `${OPENSEARCH_DASHBOARDS_URL}/${pathname}`; Cypress.log({ @@ -60,7 +60,7 @@ Cypress.Commands.add( } ); -Cypress.Commands.add('createDetector', (detectorJSON) => { +Cypress.Commands.add('sa_createDetector', (detectorJSON) => { cy.request( 'POST', `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}`, @@ -69,7 +69,7 @@ Cypress.Commands.add('createDetector', (detectorJSON) => { }); Cypress.Commands.add( - 'createAliasMappings', + 'sa_createAliasMappings', (indexName, ruleTopic, aliasMappingsBody, partial = true) => { const body = { index_name: indexName, @@ -85,7 +85,7 @@ Cypress.Commands.add( } ); -Cypress.Commands.add('updateDetector', (detectorId, detectorJSON) => { +Cypress.Commands.add('sa_updateDetector', (detectorId, detectorJSON) => { cy.request( 'PUT', `${BACKEND_BASE_PATH}${NODE_API.DETECTORS_BASE}/${detectorId}`, @@ -93,7 +93,7 @@ Cypress.Commands.add('updateDetector', (detectorId, detectorJSON) => { ); }); -Cypress.Commands.add('deleteDetector', (detectorName) => { +Cypress.Commands.add('sa_deleteDetector', (detectorName) => { const body = { from: 0, size: 5000, @@ -125,7 +125,7 @@ Cypress.Commands.add('deleteDetector', (detectorName) => { }); }); -Cypress.Commands.add('deleteAllDetectors', () => { +Cypress.Commands.add('sa_deleteAllDetectors', () => { cy.request({ method: 'DELETE', url: `${BACKEND_BASE_PATH}/.opensearch-sap-detectors-config`, @@ -136,58 +136,58 @@ Cypress.Commands.add('deleteAllDetectors', () => { }); }); -Cypress.Commands.add('getElementByText', (locator, text) => { +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('getButtonByText', (text) => { +Cypress.Commands.add('sa_getButtonByText', (text) => { Cypress.log({ message: `Get button by text: ${text}` }); - return cy.getElementByText('.euiButton', text); + return cy.sa_getElementByText('.euiButton', text); }); -Cypress.Commands.add('getInputByPlaceholder', (placeholder) => { +Cypress.Commands.add('sa_getInputByPlaceholder', (placeholder) => { Cypress.log({ message: `Get input element by placeholder: ${placeholder}` }); return cy.get(`input[placeholder="${placeholder}"]`); }); -Cypress.Commands.add('getComboboxByPlaceholder', (placeholder) => { +Cypress.Commands.add('sa_getComboboxByPlaceholder', (placeholder) => { Cypress.log({ message: `Get combobox element by placeholder: ${placeholder}`, }); return cy - .getElementByText('.euiComboBoxPlaceholder', placeholder) + .sa_getElementByText('.euiComboBoxPlaceholder', placeholder) .siblings('.euiComboBox__input') .find('input'); }); -Cypress.Commands.add('getFieldByLabel', (label, type = 'input') => { +Cypress.Commands.add('sa_getFieldByLabel', (label, type = 'input') => { Cypress.log({ message: `Get field by label: ${label}` }); return cy - .getElementByText('.euiFormRow__labelWrapper', label) + .sa_getElementByText('.euiFormRow__labelWrapper', label) .siblings() .find(type); }); -Cypress.Commands.add('getTextareaByLabel', (label) => { +Cypress.Commands.add('sa_getTextareaByLabel', (label) => { Cypress.log({ message: `Get textarea by label: ${label}` }); - return cy.getFieldByLabel(label, 'textarea'); + return cy.sa_getFieldByLabel(label, 'textarea'); }); -Cypress.Commands.add('getElementByTestSubject', (subject) => { +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('getRadioButtonById', (id) => { +Cypress.Commands.add('sa_getRadioButtonById', (id) => { Cypress.log({ message: `Get radio button by id: ${id}` }); return cy.get(`input[id="${id}"]`); }); Cypress.Commands.add( - 'selectComboboxItem', + 'sa_selectComboboxItem', { prevSubject: true, }, @@ -196,12 +196,15 @@ Cypress.Commands.add( items = [items]; } Cypress.log({ message: `Select combobox items: ${items.join(' | ')}` }); - items.map((item) => cy.wrap(subject).type(item).type('{enter}')); + items.map((item) => { + cy.wrap(subject).type(item); + cy.get(`[title="${item}"]`).click({ force: true }); + }); } ); Cypress.Commands.add( - 'clearCombobox', + 'sa_clearCombobox', { prevSubject: true, }, @@ -215,7 +218,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'containsValue', + 'sa_containsValue', { prevSubject: true, }, @@ -226,7 +229,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'clearValue', + 'sa_clearValue', { prevSubject: true, }, @@ -234,7 +237,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'containsError', + 'sa_containsError', { prevSubject: true, }, @@ -247,7 +250,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'containsHelperText', + 'sa_containsHelperText', { prevSubject: true, }, @@ -260,7 +263,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'shouldNotHaveError', + 'sa_shouldNotHaveError', { prevSubject: true, }, @@ -272,24 +275,24 @@ Cypress.Commands.add( .should('not.exist') ); -Cypress.Commands.add('validateDetailsItem', (label, value) => { +Cypress.Commands.add('sa_validateDetailsItem', (label, value) => { Cypress.log({ message: `Validate details item by label: ${label} and value: ${value}`, }); return cy - .getElementByText('.euiFlexItem label', label) + .sa_getElementByText('.euiFlexItem label', label) .parent() .siblings() .contains(value); }); -Cypress.Commands.add('urlShouldContain', (path) => { +Cypress.Commands.add('sa_urlShouldContain', (path) => { Cypress.log({ message: `Url should contain path: ${path}` }); return cy.url().should('contain', `#/${path}`); }); Cypress.Commands.add( - 'pressEnterKey', + 'sa_pressEnterKey', { prevSubject: true, }, @@ -311,7 +314,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'validateTable', + 'sa_validateTable', { prevSubject: true, }, @@ -352,12 +355,12 @@ Cypress.Commands.add('sa_createIndex', (index, settings = {}) => { ); }); -Cypress.Commands.add('ingestDocument', (indexId, documentJSON) => { +Cypress.Commands.add('sa_ingestDocument', (indexId, documentJSON) => { cy.request('POST', `${BACKEND_BASE_PATH}/${indexId}/_doc`, documentJSON); }); Cypress.Commands.add( - 'insertDocumentToIndex', + 'sa_insertDocumentToIndex', (indexName, documentId, documentBody) => { cy.request({ method: 'POST', @@ -379,7 +382,7 @@ Cypress.Commands.add('sa_deleteAllIndices', () => { }); }); -Cypress.Commands.add('createRule', (ruleJSON) => { +Cypress.Commands.add('sa_createRule', (ruleJSON) => { return cy.request({ method: 'POST', url: `${OPENSEARCH_DASHBOARDS}${NODE_API.RULES_BASE}?category=${ruleJSON.category}`, @@ -390,7 +393,7 @@ Cypress.Commands.add('createRule', (ruleJSON) => { }); }); -Cypress.Commands.add('updateRule', (ruleId, ruleJSON) => { +Cypress.Commands.add('sa_updateRule', (ruleId, ruleJSON) => { cy.request( 'PUT', `${BACKEND_BASE_PATH}${NODE_API.RULES_BASE}/${ruleId}`, @@ -398,7 +401,7 @@ Cypress.Commands.add('updateRule', (ruleId, ruleJSON) => { ); }); -Cypress.Commands.add('deleteRule', (ruleName) => { +Cypress.Commands.add('sa_deleteRule', (ruleName) => { const body = { from: 0, size: 5000, @@ -431,7 +434,7 @@ Cypress.Commands.add('deleteRule', (ruleName) => { }); }); -Cypress.Commands.add('deleteAllCustomRules', () => { +Cypress.Commands.add('sa_deleteAllCustomRules', () => { const url = `${BACKEND_BASE_PATH}/.opensearch-sap-custom-rules-config`; cy.request({ method: 'DELETE', @@ -445,17 +448,17 @@ Cypress.Commands.add('deleteAllCustomRules', () => { }); Cypress.Commands.add( - 'ospSearch', + 'sa_ospSearch', { prevSubject: true, }, (subject, text) => { - return cy.get(subject).clear().ospType(text).realPress('Enter'); + return cy.get(subject).clear().sa_ospType(text).realPress('Enter'); } ); Cypress.Commands.add( - 'ospClear', + 'sa_ospClear', { prevSubject: true, }, @@ -470,7 +473,7 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'ospType', + 'sa_ospType', { prevSubject: true, }, diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js index d7b7595c9..79c637364 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/constants.js @@ -80,13 +80,13 @@ export const createDetector = ( ], }; - cy.cleanUpTests() + cy.sa_cleanUpTests() // Create test index .then(() => cy.sa_createIndex(indexName, indexSettings)) // Create field mappings .then(() => - cy.createAliasMappings( + cy.sa_createAliasMappings( indexName, detectorConfig.detector_type, indexMappings, @@ -95,14 +95,14 @@ export const createDetector = ( ) // Create rule .then(() => { - cy.createRule(ruleSettings) + 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.createDetector(detectorConfig)); + .then(() => cy.sa_createDetector(detectorConfig)); }) .then(() => { // Go to the detectors table page @@ -121,7 +121,7 @@ export const createDetector = ( cy.wait(65000); // Ingest documents to the test index for (let i = 0; i < indexDocsCount; i++) { - cy.insertDocumentToIndex(indexName, '', indexDoc); + cy.sa_insertDocumentToIndex(indexName, '', indexDoc); } return detectorConfig; diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts index c4805c8cc..5e0eb0119 100644 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts +++ b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.d.ts @@ -11,198 +11,198 @@ declare namespace Cypress { /** * Returns element by its text * @example - * cy.getElementByText('.euiTitle', 'Some title') + * cy.sa_getElementByText('.euiTitle', 'Some title') */ - getElementByText(locator: string, text: string): Chainable; + sa_getElementByText(locator: string, text: string): Chainable; /** * Returns button by its text * @example - * cy.getButtonByText('Button text') + * cy.sa_getButtonByText('Button text') */ - getButtonByText(text: string): Chainable; + sa_getButtonByText(text: string): Chainable; /** * Returns input by its placeholder * @example - * cy.getInputByPlaceholder('Search rules...') + * cy.sa_getInputByPlaceholder('Search rules...') */ - getInputByPlaceholder(placeholder: string): Chainable; + sa_getInputByPlaceholder(placeholder: string): Chainable; /** * Returns combobox input by its placeholder * @example - * cy.getComboboxByPlaceholder('Select data input...') + * cy.sa_getComboboxByPlaceholder('Select data input...') */ - getComboboxByPlaceholder(placeholder: string): Chainable; + sa_getComboboxByPlaceholder(placeholder: string): Chainable; /** * Returns field input by label * @example - * cy.getFieldByLabel('Detector name') + * cy.sa_getFieldByLabel('Detector name') */ - getFieldByLabel(label: string, type?: string): Chainable; + sa_getFieldByLabel(label: string, type?: string): Chainable; /** * Returns textarea by label * @example - * cy.getTextareaByLabel('Detector description') + * cy.sa_getTextareaByLabel('Detector description') */ - getTextareaByLabel(label: string): Chainable; + sa_getTextareaByLabel(label: string): Chainable; /** * Returns element by data-test-subj attribute value * @example - * cy.getElementByTestSubject('alerts-input-element') + * cy.sa_getElementByTestSubject('alerts-input-element') */ - getElementByTestSubject(subject: string): Chainable; + sa_getElementByTestSubject(subject: string): Chainable; /** * Returns radio by id * @example - * cy.getRadioButtonById('radioId') + * cy.sa_getRadioButtonById('radioId') */ - getRadioButtonById(id: string): Chainable; + sa_getRadioButtonById(id: string): Chainable; /** * Selects combobox item(s) * @example - * cy.get('combo).selectComboboxItem('some item value') + * cy.get('combo).sa_selectComboboxItem('some item value') */ - selectComboboxItem(items: string | string[]): Chainable; + sa_selectComboboxItem(items: string | string[]): Chainable; /** * Clears combobox value(s) * @example - * cy.get('combo).clearCombobox() + * cy.get('combo).sa_clearCombobox() */ - clearCombobox(): Chainable; + sa_clearCombobox(): Chainable; /** * Triggers enter key event on the focused element * @example - * cy.pressEnterKey() + * cy.sa_pressEnterKey() */ - pressEnterKey(): Chainable; + sa_pressEnterKey(): Chainable; /** * Triggers backspace key event on the focused element * @example - * cy.pressBackspaceKey() + * cy.sa_pressBackspaceKey() */ - pressBackspaceKey(numberOfPresses?: number): Chainable; + sa_pressBackspaceKey(numberOfPresses?: number): Chainable; /** * Validates details panel item * @example - * cy.validateDetailsItem('Data source', '.index-name') + * cy.sa_validateDetailsItem('Data source', '.index-name') */ - validateDetailsItem(label: string, value: string): Chainable; + sa_validateDetailsItem(label: string, value: string): Chainable; /** * Should clear a field value (use with text and textarea fields) * @example - * cy.getFieldByLabel('Rule name').clearValue() + * cy.sa_getFieldByLabel('Rule name').sa_clearValue() */ - clearValue(): Chainable; + 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.getFieldByLabel('Rule name').containsValue('Name') + * cy.sa_getFieldByLabel('Rule name').sa_containsValue('Name') */ - containsValue(value: string): Chainable; + sa_containsValue(value: string): Chainable; /** * Validates that field has error text * @example - * cy.getFieldByLabel('Rule name').containsError('This fields is invalid') + * cy.sa_getFieldByLabel('Rule name').sa_containsError('This fields is invalid') */ - containsError(errorText: string): Chainable; + sa_containsError(errorText: string): Chainable; /** * Validates that field has helper text * @example - * cy.getFieldByLabel('Rule name').containsHelperText('Use this field for...') + * cy.sa_getFieldByLabel('Rule name').sa_containsHelperText('Use this field for...') */ - containsHelperText(helperText: string): Chainable; + sa_containsHelperText(helperText: string): Chainable; /** * Should not have error text * @example - * cy.getFieldByLabel('Rule name').shouldNotHaveError() + * cy.sa_getFieldByLabel('Rule name').sa_shouldNotHaveError() */ - shouldNotHaveError(): Chainable; + sa_shouldNotHaveError(): Chainable; /** * Validates url path * @example - * cy.urlShouldContain('/detector-details') + * cy.sa_urlShouldContain('/detector-details') */ - urlShouldContain(path: string): Chainable; + sa_urlShouldContain(path: string): Chainable; /** * Validates table items * @example - * cy.validateTable('/detector-details') + * cy.sa_validateTable('/detector-details') */ - validateTable(data: { [key: string]: string }[]): Chainable; + sa_validateTable(data: { [key: string]: string }[]): Chainable; /** * Removes custom indices, detectors and rules * @example - * cy.cleanUpTests() + * cy.sa_cleanUpTests() */ - cleanUpTests(): Chainable; + sa_cleanUpTests(): Chainable; /** * Returns table first row * Finds elements deeper in a row with selector * @param {string} selector * @example - * cy.getTableFirstRow() - * cy.getTableFirstRow('td') + * cy.sa_getTableFirstRow() + * cy.sa_getTableFirstRow('td') */ - getTableFirstRow(selector: string): Chainable; + sa_getTableFirstRow(selector: string): Chainable; /** * Waits for page to be loaded * @param {string} pathname * @param {any} opts * @example - * cy.waitForPageLoad('detectors') - * cy.waitForPageLoad('detectors', { + * cy.sa_waitForPageLoad('detectors') + * cy.sa_waitForPageLoad('detectors', { * timeout: 20000, * contains: 'text to verify' * }) */ - waitForPageLoad(pathname: string, opts?: any): Chainable; + 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').ospSearch('Txt to write into input') + * cy.get('selector').sa_ospSearch('Txt to write into input') */ - ospSearch(text: string): Chainable; + sa_ospSearch(text: string): Chainable; /** * Clears input text * @example - * cy.get('selector').ospClear() + * cy.get('selector').sa_ospClear() */ - ospClear(): Chainable; + sa_ospClear(): Chainable; /** * Returns table first row * Can find elements deeper in a row with selector * @param {string} text * @example - * cy.get('selector').ospType('Txt to write into input') + * cy.get('selector').sa_ospType('Txt to write into input') */ - ospType(text: string): Chainable; + sa_ospType(text: string): Chainable; /** * Creates index with optional settings @@ -214,9 +214,9 @@ declare namespace Cypress { /** * Creates an index template. * @example - * cy.createIndexTemplate("some_index_template", { "index_patterns": "abc", "properties": { ... } }) + * cy.sa_createIndexTemplate("some_index_template", { "index_patterns": "abc", "properties": { ... } }) */ - createIndexTemplate(name: string, template: object): Chainable; + sa_createIndexTemplate(name: string, template: object): Chainable; /** /** @@ -229,30 +229,30 @@ declare namespace Cypress { /** * Deletes all custom rules in cluster * @example - * cy.deleteAllCustomRules() + * cy.sa_deleteAllCustomRules() */ - deleteAllCustomRules(): Chainable; + sa_deleteAllCustomRules(): Chainable; /** * Deletes all detectors in cluster * @example - * cy.deleteAllDetectors() + * cy.sa_deleteAllDetectors() */ - deleteAllDetectors(): Chainable; + sa_deleteAllDetectors(): Chainable; /** * Creates a detector * @example - * cy.createPolicy({ "detector_type": ... }) + * cy.sa_createPolicy({ "detector_type": ... }) */ - createDetector(detectorJSON: object): Chainable; + sa_createDetector(detectorJSON: object): Chainable; /** * Creates a fields mapping aliases for detector * @example - * cy.createAliasMappings('indexName', 'windows', {...}, true) + * cy.sa_createAliasMappings('indexName', 'windows', {...}, true) */ - createAliasMappings( + sa_createAliasMappings( indexName: string, ruleTopic: string, aliasMappingsBody: object, @@ -262,22 +262,22 @@ declare namespace Cypress { /** * Creates a custom rule * @example - * cy.createRule({}) + * cy.sa_createRule({}) */ - createRule(ruleJSON: object): Chainable; + sa_createRule(ruleJSON: object): Chainable; /** * Updates settings for index * @example - * cy.updateIndexSettings("some_index", settings) + * cy.sa_updateIndexSettings("some_index", settings) */ - updateDetector(detectorId: string, detectorJSON: object): Chainable; + sa_updateDetector(detectorId: string, detectorJSON: object): Chainable; /** * Deletes detector by its name * @example - * cy.deleteDetector("Cypress detector name") + * cy.sa_deleteDetector("Cypress detector name") */ - deleteDetector(name: string): Chainable; + sa_deleteDetector(name: string): Chainable; } } From 193ec7aaa00bff7c792938b86a41b6d9e67f6b50 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Tue, 10 Oct 2023 13:21:35 -0700 Subject: [PATCH 9/9] addressed PR comments Signed-off-by: Amardeepsingh Siglani --- .../1_detectors.spec.js | 4 +-- .../index.js | 27 ------------------- 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 cypress/utils/plugins/security-analytics-dashboards-plugin/index.js 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 889460ec4..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 @@ -136,9 +136,9 @@ const validatePendingFieldMappingsPanel = (mappings) => { const fillDetailsForm = (detectorName, dataSource) => { getNameField().type(detectorName); getDataSourceField().sa_selectComboboxItem(dataSource); - getDataSourceField().blur(); + getDataSourceField().focus().blur(); getLogTypeField().sa_selectComboboxItem(cypressLogTypeDns); - getLogTypeField().blur(); + getLogTypeField().focus().blur(); }; const createDetector = (detectorName, dataSource, expectFailure) => { diff --git a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.js b/cypress/utils/plugins/security-analytics-dashboards-plugin/index.js deleted file mode 100644 index 435865356..000000000 --- a/cypress/utils/plugins/security-analytics-dashboards-plugin/index.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -// Import commands.js using ES2015 syntax: -import './commands'; -import 'cypress-real-events'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') - -const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; -Cypress.on('uncaught:exception', (err) => { - /* returning false here prevents Cypress from failing the test */ - if (resizeObserverLoopErrRe.test(err.message)) { - return false; - } -}); - -// Switch the base URL of Opensearch when security enabled in the cluster -// Not doing this for Dashboards because it can still use http when security enabled -if (Cypress.env('SECURITY_ENABLED')) { - Cypress.env('opensearch', `https://${Cypress.env('openSearchUrl')}`); -} else { - Cypress.env('opensearch', `http://${Cypress.env('openSearchUrl')}`); -}