From ad462880a352c4087278784a340e1c8af37185a9 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 18:39:42 -0700 Subject: [PATCH] [Custom log types] Support custom log types in detection rule creation and detector creation (#676) (#682) * log types table; details page with basic editing Signed-off-by: Amardeepsingh Siglani * implemented create, delete of log type Signed-off-by: Amardeepsingh Siglani * detection rules; delete modals added Signed-off-by: Amardeepsingh Siglani * updated snapshots Signed-off-by: Amardeepsingh Siglani * addressed PR comments Signed-off-by: Amardeepsingh Siglani * updated cypress workflow Signed-off-by: Amardeepsingh Siglani * use log types from API Signed-off-by: Amardeepsingh Siglani * updated detector cypress test Signed-off-by: Amardeepsingh Siglani * using api provided log types in rule creation Signed-off-by: Amardeepsingh Siglani * addressed PR comments Signed-off-by: Amardeepsingh Siglani * updated cypress test Signed-off-by: Amardeepsingh Siglani * updated nav panel Signed-off-by: Amardeepsingh Siglani * fixed log type labels; cypress tests Signed-off-by: Amardeepsingh Siglani --------- Signed-off-by: Amardeepsingh Siglani (cherry picked from commit 53885963461ec2f0828062bb5b9d9ee627c5dcc1) Co-authored-by: Amardeepsingh Siglani Signed-off-by: AWSHurneyt --- cypress/integration/1_detectors.spec.js | 10 +- cypress/integration/3_alerts.spec.js | 2 +- cypress/integration/4_findings.spec.js | 2 +- .../containers/CorrelationsContainer.tsx | 4 +- .../containers/CreateCorrelationRule.tsx | 18 +- public/pages/Correlations/utils/constants.tsx | 15 +- .../components/DetectorType/DetectorType.tsx | 37 ++- .../DetectorRulesView.test.tsx.snap | 236 +-------------- .../DetectorDetails.test.tsx.snap | 236 +-------------- .../DetectorDetailsView.test.tsx.snap | 236 +-------------- public/pages/Detectors/models/interfaces.ts | 5 - public/pages/Detectors/utils/constants.ts | 17 -- .../LogTypes/components/LogTypeDetailsTab.tsx | 2 +- .../pages/LogTypes/components/LogTypeForm.tsx | 5 +- .../LogTypes/containers/CreateLogType.tsx | 2 +- public/pages/LogTypes/containers/LogType.tsx | 28 +- .../LogTypes/containers/LogTypeDetails.tsx | 137 --------- public/pages/Main/Main.tsx | 18 +- .../components/RuleEditor/RuleEditorForm.tsx | 76 +++-- .../RuleEditorContainer.test.tsx.snap | 286 +++++++++++------- .../RuleEditorForm.test.tsx.snap | 286 +++++++++++------- public/pages/Rules/utils/constants.ts | 17 +- public/pages/Rules/utils/helpers.tsx | 3 +- public/security_analytics_app.tsx | 35 ++- public/services/LogTypeService.ts | 18 +- public/store/LogTypeStore.ts | 28 +- public/store/RulesStore.ts | 4 +- public/utils/constants.ts | 3 +- public/utils/helpers.tsx | 4 +- server/services/RuleService.ts | 5 +- types/Detector.ts | 5 - types/LogTypes.ts | 8 +- 32 files changed, 566 insertions(+), 1222 deletions(-) delete mode 100644 public/pages/LogTypes/containers/LogTypeDetails.tsx diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js index dda870d3d..609615dc6 100644 --- a/cypress/integration/1_detectors.spec.js +++ b/cypress/integration/1_detectors.spec.js @@ -15,6 +15,7 @@ import { getMappingFields } from '../../public/pages/Detectors/utils/helpers'; const cypressIndexDns = 'cypress-index-dns'; const cypressIndexWindows = 'cypress-index-windows'; const detectorName = 'test detector'; +const cypressLogTypeDns = 'dns'; const cypressDNSRule = dns_name_rule_data.title; @@ -24,8 +25,6 @@ const getNextButton = () => cy.getButtonByText('Next'); const getCreateDetectorButton = () => cy.getButtonByText('Create detector'); -const selectDnsLogType = () => cy.getRadioButtonById('dns').click({ force: true }); - const validateAlertPanel = (alertName) => cy .getElementByText('.euiTitle', 'Alert triggers') @@ -38,6 +37,10 @@ const dataSourceLabel = 'Select or input source indexes or index patterns'; const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel); +const logTypeLabel = 'Select a log type you would like to detect'; + +const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel); + const openDetectorDetails = (detectorName) => { cy.getInputByPlaceholder('Search threat detectors').type(`${detectorName}`).pressEnterKey(); cy.getElementByText('.euiTableCellContent button', detectorName).click(); @@ -112,7 +115,8 @@ const validatePendingFieldMappingsPanel = (mappings) => { const fillDetailsForm = (detectorName, dataSource) => { getNameField().type(detectorName); getDataSourceField().selectComboboxItem(dataSource); - selectDnsLogType(); + getLogTypeField().selectComboboxItem(cypressLogTypeDns); + getLogTypeField().blur(); }; const createDetector = (detectorName, dataSource, expectFailure) => { diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index 9d3403491..3476d78e0 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -118,7 +118,7 @@ describe('Alerts', () => { expect($tr, `timestamp`).to.contain(date); expect($tr, `rule name`).to.contain('Cypress USB Rule'); expect($tr, `detector name`).to.contain(testDetector.name); - expect($tr, `log type`).to.contain('Windows'); + expect($tr, `log type`).to.contain('windows'); }); // Close the flyout diff --git a/cypress/integration/4_findings.spec.js b/cypress/integration/4_findings.spec.js index eec8234b8..59114b36a 100644 --- a/cypress/integration/4_findings.spec.js +++ b/cypress/integration/4_findings.spec.js @@ -52,7 +52,7 @@ describe('Findings', () => { cy.contains('No items found').should('not.exist'); // Check for expected findings - cy.contains('Windows'); + cy.contains('windows'); cy.contains('High'); }); diff --git a/public/pages/Correlations/containers/CorrelationsContainer.tsx b/public/pages/Correlations/containers/CorrelationsContainer.tsx index 9270e7377..5647dcad9 100644 --- a/public/pages/Correlations/containers/CorrelationsContainer.tsx +++ b/public/pages/Correlations/containers/CorrelationsContainer.tsx @@ -6,7 +6,7 @@ import { CorrelationFinding, CorrelationGraphData, DateTimeFilter } from '../../ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { - defaultLogTypeFilterItemOptions, + getDefaultLogTypeFilterItemOptions, defaultSeverityFilterItemOptions, emptyGraphData, getAbbrFromLogType, @@ -85,7 +85,7 @@ export class Correlations extends React.Component = ( isInvalid={isInvalidInputForQuery('logType')} placeholder="Select a log type" data-test-subj={'rule_type_dropdown'} - options={ruleTypes.map(({ value, label }) => ({ value, label }))} + options={ruleTypes.map(({ label }) => ({ + value: label.toLowerCase(), + label, + }))} singleSelection={{ asPlainText: true }} onChange={(e) => { props.handleChange(`queries[${queryIdx}].logType`)( @@ -283,7 +286,18 @@ export const CreateCorrelationRule: React.FC = ( }} onBlur={props.handleBlur(`queries[${queryIdx}].logType`)} selectedOptions={ - query.logType ? [{ value: query.logType, label: query.logType }] : [] + query.logType + ? [ + { + value: query.logType, + label: + ruleTypes.find( + (logType) => + logType.label.toLowerCase() === query.logType.toLowerCase() + )?.label || query.logType, + }, + ] + : [] } isClearable={true} onCreateOption={(e) => { diff --git a/public/pages/Correlations/utils/constants.tsx b/public/pages/Correlations/utils/constants.tsx index 0b95f4f44..46d5d58e6 100644 --- a/public/pages/Correlations/utils/constants.tsx +++ b/public/pages/Correlations/utils/constants.tsx @@ -47,15 +47,12 @@ export const graphRenderOptions = { }, }; -export const defaultLogTypeFilterItemOptions: FilterItem[] = Object.values(ruleTypes).map( - (type) => { - return { - name: `${type.abbr}: ${type.label}`, - id: type.value, - checked: 'on', - }; - } -); +export const getDefaultLogTypeFilterItemOptions: () => FilterItem[] = () => + Object.values(ruleTypes).map((type) => ({ + name: `${type.label}`, + id: type.label.toLowerCase(), + checked: 'on', + })); export const defaultSeverityFilterItemOptions: FilterItem[] = Object.values(ruleSeverity).map( (sev) => { diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx index aeb6e9fa6..be60c2bdf 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorType/DetectorType.tsx @@ -5,12 +5,11 @@ import React, { Component } from 'react'; import { ContentPanel } from '../../../../../../components/ContentPanel'; -import { EuiFormRow, EuiFlexGrid, EuiFlexItem, EuiRadio, EuiSpacer } from '@elastic/eui'; +import { EuiFormRow, EuiSpacer, EuiComboBox } from '@elastic/eui'; import { FormFieldHeader } from '../../../../../../components/FormFieldHeader/FormFieldHeader'; -import { DETECTOR_TYPES } from '../../../../../Detectors/utils/constants'; -import { DetectorTypeOption } from '../../../../../Detectors/models/interfaces'; import { CreateDetectorRulesState, DetectionRules } from '../DetectionRules/DetectionRules'; import { RuleItem } from '../DetectionRules/types/interfaces'; +import { ruleTypes } from '../../../../../Rules/utils/constants'; interface DetectorTypeProps { detectorType: string; @@ -24,19 +23,18 @@ interface DetectorTypeProps { interface DetectorTypeState { fieldTouched: boolean; - detectorTypeOptions: DetectorTypeOption[]; detectorTypeIds: string[]; } export default class DetectorType extends Component { + private detectorTypeOptions: { value: string; label: string }[]; constructor(props: DetectorTypeProps) { super(props); - const detectorTypeOptions = Object.values(DETECTOR_TYPES); - const detectorTypeIds = detectorTypeOptions.map((option) => option.id); + this.detectorTypeOptions = ruleTypes.map(({ label }) => ({ value: label, label })); + const detectorTypeIds = this.detectorTypeOptions.map((option) => option.value); this.state = { fieldTouched: false, - detectorTypeOptions, detectorTypeIds, }; } @@ -63,17 +61,6 @@ export default class DetectorType extends Component ( - - this.onChange(type.id)} - /> - - )); return ( - + } @@ -92,7 +79,17 @@ export default class DetectorType extends Component - {radioButtons} + { + this.onChange(e[0]?.label || ''); + }} + selectedOptions={detectorType ? [{ value: detectorType, label: detectorType }] : []} + /> diff --git a/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap b/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap index 0af63b761..3eb63c7c4 100644 --- a/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap +++ b/public/pages/Detectors/components/DetectorRulesView/__snapshots__/DetectorRulesView.test.tsx.snap @@ -416,64 +416,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -582,64 +525,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -820,64 +706,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -980,64 +809,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", } } diff --git a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap index ba0cebeac..0c54d9ea5 100644 --- a/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap +++ b/public/pages/Detectors/containers/Detector/__snapshots__/DetectorDetails.test.tsx.snap @@ -2970,64 +2970,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -3136,64 +3079,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -3374,64 +3260,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -3534,64 +3363,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", } } diff --git a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap index f3f544a8d..a9d72d22b 100644 --- a/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap +++ b/public/pages/Detectors/containers/DetectorDetailsView/__snapshots__/DetectorDetailsView.test.tsx.snap @@ -1794,64 +1794,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -1960,64 +1903,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -2198,64 +2084,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", }, Object { @@ -2358,64 +2187,7 @@ exports[` spec renders the component 1`] = ` "field": "category", "multiSelect": "or", "name": "Log type", - "options": Array [ - Object { - "name": "Network", - "value": "network", - }, - Object { - "name": "DNS", - "value": "dns", - }, - Object { - "name": "Apache Access", - "value": "apache_access", - }, - Object { - "name": "Windows", - "value": "windows", - }, - Object { - "name": "AD/LDAP", - "value": "ad_ldap", - }, - Object { - "name": "Linux", - "value": "linux", - }, - Object { - "name": "Cloudtrail", - "value": "cloudtrail", - }, - Object { - "name": "S3", - "value": "s3", - }, - Object { - "name": "Google Workspace", - "value": "gworkspace", - }, - Object { - "name": "Github actions", - "value": "github", - }, - Object { - "name": "Microsoft 365", - "value": "m365", - }, - Object { - "name": "Okta", - "value": "okta", - }, - Object { - "name": "Azure", - "value": "azure", - }, - Object { - "name": "VPC Flow", - "value": "vpcflow", - }, - ], + "options": Array [], "type": "field_value_selection", } } diff --git a/public/pages/Detectors/models/interfaces.ts b/public/pages/Detectors/models/interfaces.ts index cc7961374..190e3833c 100644 --- a/public/pages/Detectors/models/interfaces.ts +++ b/public/pages/Detectors/models/interfaces.ts @@ -6,8 +6,3 @@ export interface IndexOption { label: string; } - -export interface DetectorTypeOption { - id: string; - label: string; -} diff --git a/public/pages/Detectors/utils/constants.ts b/public/pages/Detectors/utils/constants.ts index 4766b17d2..741e2309c 100644 --- a/public/pages/Detectors/utils/constants.ts +++ b/public/pages/Detectors/utils/constants.ts @@ -25,20 +25,3 @@ export const EMPTY_DEFAULT_DETECTOR_INPUT = { rules: [], }, }; - -export const DETECTOR_TYPES = { - NETWORK: { id: 'network', label: 'Network events', abbr: 'NTW' }, - DNS: { id: 'dns', label: 'DNS logs', abbr: 'DNS' }, - APACHE_ACCESS: { id: 'apache_access', label: 'Apache access logs', abbr: 'APC' }, - WINDOWS: { id: 'windows', label: 'Windows logs', abbr: 'WIN' }, - AD_LDAP: { id: 'ad_ldap', label: 'AD/LDAP logs', abbr: 'AD' }, - SYSTEM: { id: 'linux', label: 'System logs', abbr: 'LNX' }, - CLOUD_TRAIL: { id: 'cloudtrail', label: 'Cloud Trail logs', abbr: 'CLT' }, - S3: { id: 's3', label: 'S3 access logs', abbr: 'S3' }, - GWORKSPACE: { id: 'gworkspace', label: 'Google Workspace logs', abbr: 'GGL' }, - GITHUB: { id: 'github', label: 'Github actions', abbr: 'GHB' }, - M365: { id: 'm365', label: 'Microsoft 365 logs', abbr: 'MSO' }, - OKTA: { id: 'okta', label: 'Okta events', abbr: 'OKT' }, - AZURE: { id: 'azure', label: 'Azure logs', abbr: 'AZR' }, - VPC_FLOW: { id: 'vpcflow', label: 'VPC Flow logs', abbr: 'VPC' }, -}; diff --git a/public/pages/LogTypes/components/LogTypeDetailsTab.tsx b/public/pages/LogTypes/components/LogTypeDetailsTab.tsx index 04504f35e..136c5726c 100644 --- a/public/pages/LogTypes/components/LogTypeDetailsTab.tsx +++ b/public/pages/LogTypes/components/LogTypeDetailsTab.tsx @@ -64,7 +64,7 @@ export const LogTypeDetailsTab: React.FC = ({ }) } placeholder="Enter name for log type" - disabled={!isEditMode || !!logTypeDetails.detectionRules} + disabled={!isEditMode || !!logTypeDetails.detectionRulesCount} /> diff --git a/public/pages/LogTypes/components/LogTypeForm.tsx b/public/pages/LogTypes/components/LogTypeForm.tsx index ce0b6ebb4..56d6a98f3 100644 --- a/public/pages/LogTypes/components/LogTypeForm.tsx +++ b/public/pages/LogTypes/components/LogTypeForm.tsx @@ -84,7 +84,8 @@ export const LogTypeForm: React.FC = ({ updateErrors(newLogType); }} placeholder="Enter name for the log type" - disabled={!isEditMode || !!logTypeDetails.detectionRules} + readOnly={!isEditMode} + disabled={isEditMode && !!logTypeDetails.detectionRulesCount} /> @@ -100,7 +101,7 @@ export const LogTypeForm: React.FC = ({ updateErrors(newLogType); }} placeholder="Description of the log type" - disabled={!isEditMode} + readOnly={!isEditMode} /> {isEditMode ? ( diff --git a/public/pages/LogTypes/containers/CreateLogType.tsx b/public/pages/LogTypes/containers/CreateLogType.tsx index 1d11974ba..01d4d8d1e 100644 --- a/public/pages/LogTypes/containers/CreateLogType.tsx +++ b/public/pages/LogTypes/containers/CreateLogType.tsx @@ -48,7 +48,7 @@ export const CreateLogType: React.FC = ({ history, notificat hideHeaderBorder={true} > = ({ notifications, history }) => { const updateRules = useCallback(async (details: LogTypeItem, intialDetails: LogTypeItem) => { const rulesRes = await DataStore.rules.getAllRules({ - 'rule.category': [logTypeId], + 'rule.category': [details.name.toLowerCase()], }); const ruleItems = rulesRes.map((rule) => ({ title: rule._source.title, @@ -74,22 +74,15 @@ export const LogType: React.FC = ({ notifications, history }) => { setLoadingRules(false); setLogTypeDetails({ ...details, - detectionRules: ruleItems.length, + detectionRulesCount: ruleItems.length, }); setInitialLogTypeDetails({ ...intialDetails, - detectionRules: ruleItems.length, + detectionRulesCount: ruleItems.length, }); }, []); useEffect(() => { - context?.chrome.setBreadcrumbs([ - BREADCRUMBS.SECURITY_ANALYTICS, - BREADCRUMBS.DETECTORS, - BREADCRUMBS.LOG_TYPES, - { text: logTypeId }, - ]); - const getLogTypeDetails = async () => { const details = await DataStore.logTypes.getLogType(logTypeId); @@ -98,7 +91,14 @@ export const LogType: React.FC = ({ notifications, history }) => { return; } - updateRules(details, details); + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.DETECTORS, + BREADCRUMBS.LOG_TYPES, + { text: details.name }, + ]); + const logTypeItem = { ...details, detectionRulesCount: details.detectionRules.length }; + updateRules(logTypeItem, logTypeItem); }; getLogTypeDetails(); @@ -152,7 +152,7 @@ export const LogType: React.FC = ({ notifications, history }) => { {showDeleteModal && ( setShowDeleteModal(false)} onConfirm={deleteLogType} /> @@ -186,7 +186,9 @@ export const LogType: React.FC = ({ notifications, history }) => { diff --git a/public/pages/LogTypes/containers/LogTypeDetails.tsx b/public/pages/LogTypes/containers/LogTypeDetails.tsx deleted file mode 100644 index 8bff13635..000000000 --- a/public/pages/LogTypes/containers/LogTypeDetails.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useContext } from 'react'; -import { useState } from 'react'; -import { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; -import { LogTypeItem } from '../../../../types'; -import { - EuiDescriptionList, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPanel, - EuiSpacer, - EuiTab, - EuiTabs, - EuiTitle, -} from '@elastic/eui'; -import { DataStore } from '../../../store/DataStore'; -import { CoreServicesContext } from '../../../components/core_services'; -import { BREADCRUMBS } from '../../../utils/constants'; -import { logTypeDetailsTabs } from '../utils/constants'; -import { LogTypeDetailsTab } from '../components/LogTypeDetailsTab'; - -export interface LogTypeDetailsProps {} - -export const LogTypeDetails: React.FC = () => { - const context = useContext(CoreServicesContext); - const { logTypeId } = useParams<{ logTypeId: string }>(); - const [selectedTabId, setSelectedTabId] = useState('details'); - const [infoText, setInfoText] = useState( - <> - Loading details   - - - ); - const [logTypeDetails, setLogTypeDetails] = useState(undefined); - const [initialLogTypeDetails, setInitialLogTypeDetails] = useState( - undefined - ); - - const [isEditMode, setIsEditMode] = useState(false); - - useEffect(() => { - const getLogTypeDetails = async () => { - const details = await DataStore.logTypes.getLogType(logTypeId); - - if (!details) { - setInfoText('Log type not found!'); - return; - } - - setInitialLogTypeDetails(details); - setLogTypeDetails(details); - }; - - context?.chrome.setBreadcrumbs([ - BREADCRUMBS.SECURITY_ANALYTICS, - BREADCRUMBS.DETECTORS, - BREADCRUMBS.LOG_TYPES, - { text: logTypeId }, - ]); - getLogTypeDetails(); - }, []); - - const renderTabContent = () => { - switch (selectedTabId) { - case 'detection_rules': - return null; - case 'details': - default: - return ( - - ); - } - }; - - return !logTypeDetails ? ( - -

{infoText}

-
- ) : ( - <> - -

{logTypeDetails.name}

-
- - - - - - - - - - - - - - - - - - - {logTypeDetailsTabs.map((tab, index) => { - return ( - { - setSelectedTabId(tab.id); - }} - key={index} - isSelected={selectedTabId === tab.id} - > - {tab.name} - - ); - })} - - {renderTabContent()} - - ); -}; diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index df6adfc5e..3572bac1e 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -250,6 +250,15 @@ export default class Main extends Component { forceOpen: true, isSelected: selectedNavItemIndex === 4, items: [ + { + name: Navigation.Rules, + id: 5, + onClick: () => { + this.setState({ selectedNavItemId: 5 }); + history.push(ROUTES.RULES); + }, + isSelected: selectedNavItemIndex === 5, + }, { name: Navigation.LogTypes, id: 8, @@ -261,15 +270,6 @@ export default class Main extends Component { }, ], }, - { - name: Navigation.Rules, - id: 5, - onClick: () => { - this.setState({ selectedNavItemId: 5 }); - history.push(ROUTES.RULES); - }, - isSelected: selectedNavItemIndex === 5, - }, { name: Navigation.Correlations, id: 6, diff --git a/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx b/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx index a5b9275d3..ba652c321 100644 --- a/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx +++ b/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx @@ -19,6 +19,7 @@ import { EuiText, EuiTitle, EuiPanel, + EuiIcon, } from '@elastic/eui'; import { ContentPanel } from '../../../../components/ContentPanel'; import { FieldTextArray } from './components/FieldTextArray'; @@ -29,6 +30,8 @@ import { FormSubmissionErrorToastNotification } from './FormSubmitionErrorToastN import { YamlRuleEditorComponent } from './components/YamlRuleEditorComponent/YamlRuleEditorComponent'; import { mapFormToRule, mapRuleToForm } from './mappers'; import { DetectionVisualEditor } from './DetectionVisualEditor'; +import { useCallback } from 'react'; +import { DataStore } from '../../../../store/DataStore'; export interface VisualRuleEditorProps { initialValue: RuleEditorFormModel; @@ -62,11 +65,19 @@ export const RuleEditorForm: React.FC = ({ }) => { const [selectedEditorType, setSelectedEditorType] = useState('visual'); const [isDetectionInvalid, setIsDetectionInvalid] = useState(false); + const [logTypeOptions, setLogTypeOptions] = useState( + ruleTypes.map(({ value, label }) => ({ value, label })) + ); const onEditorTypeChange = (optionId: string) => { setSelectedEditorType(optionId); }; + const refreshLogTypeOptions = useCallback(async () => { + const logTypes = await DataStore.logTypes.getLogTypes(); + setLogTypeOptions(logTypes.map(({ id, name }) => ({ value: id, label: name }))); + }, []); + const validateTags = (fields: string[]) => { let isValid = true; let tag; @@ -255,32 +266,45 @@ export const RuleEditorForm: React.FC = ({ - - Log type - - } - isInvalid={props.touched.logType && !!props.errors?.logType} - error={props.errors.logType} - > - ({ value, label }))} - singleSelection={{ asPlainText: true }} - onChange={(e) => { - props.handleChange('logType')(e[0]?.value ? e[0].value : ''); - }} - onBlur={props.handleBlur('logType')} - selectedOptions={ - props.values.logType - ? [{ value: props.values.logType, label: props.values.logType }] - : [] - } - /> - + + + + Log type + + } + isInvalid={props.touched.logType && !!props.errors?.logType} + error={props.errors.logType} + > + { + props.handleChange('logType')(e[0]?.label ? e[0].label : ''); + }} + onFocus={refreshLogTypeOptions} + onBlur={props.handleBlur('logType')} + selectedOptions={ + props.values.logType + ? [{ value: props.values.logType, label: props.values.logType }] + : [] + } + /> + + + + + Manage + + + diff --git a/public/pages/Rules/components/RuleEditor/__snapshots__/RuleEditorContainer.test.tsx.snap b/public/pages/Rules/components/RuleEditor/__snapshots__/RuleEditorContainer.test.tsx.snap index 93e23e097..97625d2d7 100644 --- a/public/pages/Rules/components/RuleEditor/__snapshots__/RuleEditorContainer.test.tsx.snap +++ b/public/pages/Rules/components/RuleEditor/__snapshots__/RuleEditorContainer.test.tsx.snap @@ -269,84 +269,115 @@ Object { class="euiSpacer euiSpacer--l" />
- -
-
- +
- -
-
- +
- -
-
- +
- -
-
- +
- ruleTypes.find((ruleType) => ruleType.value === category)?.label || DEFAULT_EMPTY_DATA, + ruleTypes.find((ruleType) => ruleType.label.toLowerCase() === category)?.label || + DEFAULT_EMPTY_DATA, }, { field: 'source', diff --git a/public/security_analytics_app.tsx b/public/security_analytics_app.tsx index f6fb44292..000b127d2 100644 --- a/public/security_analytics_app.tsx +++ b/public/security_analytics_app.tsx @@ -29,6 +29,7 @@ import SavedObjectService from './services/SavedObjectService'; import { SecurityAnalyticsPluginStartDeps } from './plugin'; import { DataStore } from './store/DataStore'; import CorrelationService from './services/CorrelationService'; +import { LogType } from '../types'; export function renderApp( coreStart: CoreStart, @@ -68,22 +69,24 @@ export function renderApp( const isDarkMode = coreStart.uiSettings.get('theme:darkMode') || false; DataStore.init(services, coreStart.notifications); + DataStore.logTypes.getLogTypes().then((logTypes: LogType[]) => { + ReactDOM.render( + + ( + + + +
+ + + + )} + /> + , + params.element + ); + }); - ReactDOM.render( - - ( - - - -
- - - - )} - /> - , - params.element - ); return () => ReactDOM.unmountComponentAtNode(params.element); } diff --git a/public/services/LogTypeService.ts b/public/services/LogTypeService.ts index 7ab3213f4..f7be5fee2 100644 --- a/public/services/LogTypeService.ts +++ b/public/services/LogTypeService.ts @@ -28,8 +28,22 @@ export default class LogTypeService { searchLogTypes = async (id?: string): Promise> => { const url = `..${API.LOGTYPE_BASE}/_search`; - const query = id ? JSON.stringify({ terms: { _id: [id] } }) : undefined; - return (await this.httpClient.post(url, { body: query })) as ServerResponse< + const query = id + ? { + terms: { _id: [id] }, + } + : { + bool: { + must: { + query_string: { + query: + '(source: Sigma and !(name: others*) and !(name: test*)) or (source: Custom)', + }, + }, + }, + }; + const queryString = JSON.stringify(query); + return (await this.httpClient.post(url, { body: queryString })) as ServerResponse< SearchLogTypesResponse >; }; diff --git a/public/store/LogTypeStore.ts b/public/store/LogTypeStore.ts index 8f9d6cfa1..a3538916c 100644 --- a/public/store/LogTypeStore.ts +++ b/public/store/LogTypeStore.ts @@ -4,15 +4,16 @@ */ import { NotificationsStart } from 'opensearch-dashboards/public'; -import { LogType, LogTypeBase, LogTypeItem } from '../../types'; +import { LogType, LogTypeBase, LogTypeWithRules, RuleItemInfoBase } from '../../types'; import LogTypeService from '../services/LogTypeService'; import { errorNotificationToast } from '../utils/helpers'; import { DataStore } from './DataStore'; +import { ruleTypes } from '../pages/Rules/utils/constants'; export class LogTypeStore { constructor(private service: LogTypeService, private notifications: NotificationsStart) {} - public async getLogType(id: string): Promise { + public async getLogType(id: string): Promise { const logTypesRes = await this.service.searchLogTypes(id); if (logTypesRes.ok) { const logTypes: LogType[] = logTypesRes.response.hits.hits.map((hit) => { @@ -22,11 +23,16 @@ export class LogTypeStore { }; }); - const rulesRes = await DataStore.rules.getAllRules({ - 'rule.category': [id], - }); + let detectionRules: RuleItemInfoBase[] = []; + + if (logTypes[0]) { + const logTypeName = logTypes[0].name.toLowerCase(); + detectionRules = await DataStore.rules.getAllRules({ + 'rule.category': [logTypeName], + }); + } - return { ...logTypes[0], detectionRules: rulesRes.length }; + return { ...logTypes[0], detectionRules }; } return undefined; @@ -42,6 +48,16 @@ export class LogTypeStore { }; }); + ruleTypes.splice( + 0, + ruleTypes.length, + ...logTypes.map((logType) => ({ + label: logType.name, + value: logType.id, + abbr: '', + })) + ); + return logTypes; } diff --git a/public/store/RulesStore.ts b/public/store/RulesStore.ts index edb392877..0678ee196 100644 --- a/public/store/RulesStore.ts +++ b/public/store/RulesStore.ts @@ -112,8 +112,10 @@ export class RulesStore implements IRulesStore { if (!terms) { terms = { - 'rule.category': _.map(ruleTypes, 'value'), + 'rule.category': ruleTypes.map(({ label }) => label.toLowerCase()), }; + } else if (terms['rule.category']) { + terms['rule.category'] = terms['rule.category'].map((category) => category.toLowerCase()); } const body = { diff --git a/public/utils/constants.ts b/public/utils/constants.ts index 20f9a026c..9e3568a80 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -7,7 +7,6 @@ import { SimpleSavedObject } from 'opensearch-dashboards/public'; import { Detector, ServerResponse } from '../../types'; import { DetectorInput, PeriodSchedule } from '../../models/interfaces'; import { DetectorHit } from '../../server/models/interfaces'; -import { DETECTOR_TYPES } from '../pages/Detectors/utils/constants'; export const DATE_MATH_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; export const MAX_RECENTLY_USED_TIME_RANGES = 5; @@ -142,7 +141,7 @@ export const EMPTY_DEFAULT_DETECTOR_INPUT: DetectorInput = { export const EMPTY_DEFAULT_DETECTOR: Detector = { type: 'detector', - detector_type: DETECTOR_TYPES.NETWORK.id, + detector_type: 'network', name: '', enabled: true, createdBy: '', diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index 294a22d9e..4e9e11d25 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -296,8 +296,8 @@ export const getPlugins = async (opensearchService: OpenSearchService) => { export const formatRuleType = (matchingRuleType: string) => { return ( - ruleTypes.find((ruleType) => ruleType.value === matchingRuleType.toLowerCase())?.label || - DEFAULT_EMPTY_DATA + ruleTypes.find((ruleType) => ruleType.label.toLowerCase() === matchingRuleType.toLowerCase()) + ?.label || DEFAULT_EMPTY_DATA ); }; diff --git a/server/services/RuleService.ts b/server/services/RuleService.ts index af4d6fe4b..1d46741d5 100644 --- a/server/services/RuleService.ts +++ b/server/services/RuleService.ts @@ -84,7 +84,10 @@ export default class RulesService { const ruleYamlPayload = safeDump(jsonPayload); console.log(ruleYamlPayload); - const params: CreateRuleParams = { body: ruleYamlPayload, category }; + const params: CreateRuleParams = { + body: ruleYamlPayload, + category: encodeURIComponent(category), + }; const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); const createRuleResponse: CreateRuleResponse = await callWithRequest( CLIENT_RULE_METHODS.CREATE_RULE, diff --git a/types/Detector.ts b/types/Detector.ts index 1e7857f74..5b44c6164 100644 --- a/types/Detector.ts +++ b/types/Detector.ts @@ -54,11 +54,6 @@ export interface SourceIndexOption { label: string; } -export interface DetectorTypeOption { - id: string; - label: string; -} - export enum DetectorCreationStep { DEFINE_DETECTOR = 1, CONFIGURE_FIELD_MAPPING = 2, diff --git a/types/LogTypes.ts b/types/LogTypes.ts index 229c36b19..c831bbe08 100644 --- a/types/LogTypes.ts +++ b/types/LogTypes.ts @@ -3,8 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { RuleItemInfoBase } from './Rule'; + +export interface LogTypeWithRules extends LogType { + detectionRules: RuleItemInfoBase[]; +} + export interface LogTypeItem extends LogType { - detectionRules: number; + detectionRulesCount: number; } export interface LogType extends LogTypeBase {