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