Skip to content

Commit

Permalink
[2.x] Backport missing commits from #924, #942, #965 (#966)
Browse files Browse the repository at this point in the history
* Rule editor enhancements (#924)

* rule editor enhancements

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated test snapshots

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* relax validation for author name and description

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* added validation for level, status, log type in yaml editor

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fix rule creation with empty or undefined Id

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed cypress tests

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* Fetch all findings and alerts for the detectors when displaying in the tables (#942)

* fetching all findings using the pagination query params; optimized api calls for alert flyout, correlations

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fetching all alerts; updated correlations UI

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* addressed PR comments; updated snapshots

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed logic to get all alerts (#965)

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
  • Loading branch information
amsiglan authored Mar 22, 2024
1 parent 7c19a24 commit a683e9c
Show file tree
Hide file tree
Showing 60 changed files with 653 additions and 473 deletions.
6 changes: 6 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const DEFAULT_RULE_UUID = '25b9c01c-350d-4b95-bed1-836d04a4f324';
5 changes: 2 additions & 3 deletions cypress/integration/1_detectors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import _ from 'lodash';
import { getMappingFields } from '../../public/pages/Detectors/utils/helpers';
import { getLogTypeLabel } from '../../public/pages/LogTypes/utils/helpers';
import { setupIntercept } from '../support/helpers';
import { descriptionErrorString } from '../../public/utils/validation';

const cypressIndexDns = 'cypress-index-dns';
const cypressIndexWindows = 'cypress-index-windows';
Expand Down Expand Up @@ -279,9 +280,7 @@ describe('Detectors', () => {
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.'
);
.contains(descriptionErrorString);

getDescriptionField()
.type('{selectall}')
Expand Down
16 changes: 6 additions & 10 deletions cypress/integration/2_rules.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import { OPENSEARCH_DASHBOARDS_URL } from '../support/constants';
import { getLogTypeLabel } from '../../public/pages/LogTypes/utils/helpers';
import { setupIntercept } from '../support/helpers';
import { ruleDescriptionErrorString } from '../../public/utils/validation';

const uniqueId = Cypress._.random(0, 1e6);
const SAMPLE_RULE = {
name: `Cypress test rule ${uniqueId}`,
logType: 'windows',
description: 'This is a rule used to test the rule creation workflow.',
detectionLine: ['condition: Selection_1', 'Selection_1:', 'FieldKey|contains:', '- FieldValue'],
detectionLine: ['condition: Selection_1', 'Selection_1:', 'FieldKey|all:', '- FieldValue'],
severity: 'Critical',
tags: ['attack.persistence', 'attack.privilege_escalation', 'attack.t1543.003'],
references: 'https://nohello.com',
Expand Down Expand Up @@ -231,18 +232,15 @@ describe('Rules', () => {
});

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.';
const invalidDescriptionText = 'This is a invalid % description.';

getDescriptionField().should('be.empty');
getDescriptionField().type(longDescriptionText).focus().blur();
getDescriptionField().type(invalidDescriptionText).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.'
);
.contains(ruleDescriptionErrorString);

getDescriptionField()
.type('{selectall}')
Expand All @@ -268,10 +266,8 @@ describe('Rules', () => {
getAuthorField().should('be.empty');
getAuthorField().focus().blur();
getAuthorField().containsError('Author name is required');
getAuthorField().type('text').focus().blur();
getAuthorField().containsError('Invalid author.');

getAuthorField().type('{selectall}').type('{backspace}').type('tex&').focus().blur();
getAuthorField().type('{selectall}').type('{backspace}').type('tex%').focus().blur();
getAuthorField().containsError('Invalid author.');

getAuthorField()
Expand Down
19 changes: 0 additions & 19 deletions models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

export interface Rule {
id: string;
category: string;
log_source: {
product?: string;
category?: string;
service?: string;
};
title: string;
description: string;
tags: Array<{ value: string }>;
false_positives: Array<{ value: string }>;
level: string;
status: string;
references: Array<{ value: string }>;
author: string;
detection: string;
}

export interface PeriodSchedule {
period: {
interval: number;
Expand Down
22 changes: 6 additions & 16 deletions public/pages/Alerts/components/AlertFlyout/AlertFlyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
formatRuleType,
renderTime,
} from '../../../../utils/helpers';
import { FindingsService, IndexPatternsService, OpenSearchService } from '../../../../services';
import { IndexPatternsService, OpenSearchService } from '../../../../services';
import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers';
import { Finding } from '../../../Findings/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
Expand All @@ -38,7 +38,6 @@ import { Detector } from '../../../../../types';
export interface AlertFlyoutProps {
alertItem: AlertItem;
detector: Detector;
findingsService: FindingsService;
notifications: NotificationsStart;
opensearchService: OpenSearchService;
indexPatternService: IndexPatternsService;
Expand Down Expand Up @@ -71,21 +70,12 @@ export class AlertFlyout extends React.Component<AlertFlyoutProps, AlertFlyoutSt

getFindings = async () => {
this.setState({ loading: true });
const {
alertItem: { detector_id },
findingsService,
notifications,
} = this.props;
const { notifications } = this.props;
try {
const findingRes = await findingsService.getFindings({ detectorId: detector_id });
if (findingRes.ok) {
const relatedFindings = findingRes.response.findings.filter((finding) =>
this.props.alertItem.finding_ids.includes(finding.id)
);
this.setState({ findingItems: relatedFindings });
} else {
errorNotificationToast(notifications, 'retrieve', 'findings', findingRes.error);
}
const relatedFindings = await DataStore.findings.getFindingsByIds(
this.props.alertItem.finding_ids
);
this.setState({ findingItems: relatedFindings });
} catch (e: any) {
errorNotificationToast(notifications, 'retrieve', 'findings', e);
}
Expand Down
45 changes: 19 additions & 26 deletions public/pages/Alerts/containers/Alerts/Alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import {
DurationRange,
EuiBasicTableColumn,
EuiButton,
EuiButtonIcon,
Expand All @@ -18,6 +17,7 @@ import {
EuiTitle,
EuiToolTip,
EuiEmptyPrompt,
EuiTableSelectionType,
} from '@elastic/eui';
import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components/search_bar/filters/field_value_selection_filter';
import dateMath from '@elastic/datemath';
Expand Down Expand Up @@ -58,6 +58,8 @@ import { match, RouteComponentProps, withRouter } from 'react-router-dom';
import { DateTimeFilter } from '../../../Overview/models/interfaces';
import { ChartContainer } from '../../../../components/Charts/ChartContainer';
import { Detector } from '../../../../../types';
import { DurationRange } from '@elastic/eui/src/components/date_picker/types';
import { DataStore } from '../../../../store/DataStore';

export interface AlertsProps extends RouteComponentProps {
alertService: AlertsService;
Expand All @@ -66,7 +68,7 @@ export interface AlertsProps extends RouteComponentProps {
opensearchService: OpenSearchService;
notifications: NotificationsStart;
indexPatternService: IndexPatternsService;
match: match;
match: match<{ detectorId: string }>;
dateTimeFilter?: DateTimeFilter;
setDateTimeFilter?: Function;
}
Expand Down Expand Up @@ -192,14 +194,14 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
name: 'Detector',
sortable: true,
dataType: 'string',
render: (detectorName) => detectorName || DEFAULT_EMPTY_DATA,
render: (detectorName: string) => detectorName || DEFAULT_EMPTY_DATA,
},
{
field: 'state',
name: 'Status',
sortable: true,
dataType: 'string',
render: (status) => (status ? capitalizeFirstLetter(status) : DEFAULT_EMPTY_DATA),
render: (status: string) => (status ? capitalizeFirstLetter(status) : DEFAULT_EMPTY_DATA),
},
{
field: 'severity',
Expand Down Expand Up @@ -300,7 +302,7 @@ export class Alerts extends Component<AlertsProps, AlertsState> {

async getAlerts() {
this.setState({ loading: true });
const { alertService, detectorService, notifications } = this.props;
const { detectorService, notifications } = this.props;
const { detectors } = this.state;
try {
const detectorsRes = await detectorService.getDetectors();
Expand All @@ -315,26 +317,19 @@ export class Alerts extends Component<AlertsProps, AlertsState> {

for (let id of detectorIds) {
if (!detectorId || detectorId === id) {
const alertsRes = await alertService.getAlerts({ detector_id: id });

if (alertsRes.ok) {
const detectorAlerts = alertsRes.response.alerts.map((alert) => {
const detector = detectors[id];
if (!alert.detector_id) alert.detector_id = id;
return { ...alert, detectorName: detector.name };
});
alerts = alerts.concat(detectorAlerts);
} else {
errorNotificationToast(notifications, 'retrieve', 'alerts', alertsRes.error);
}
const detectorAlerts = await DataStore.alerts.getAlertsByDetector(
id,
detectors[id].name
);
alerts = alerts.concat(detectorAlerts);
}
}

this.setState({ alerts: alerts, detectors: detectors });
} else {
errorNotificationToast(notifications, 'retrieve', 'detectors', detectorsRes.error);
}
} catch (e) {
} catch (e: any) {
errorNotificationToast(notifications, 'retrieve', 'alerts', e);
}
this.filterAlerts();
Expand Down Expand Up @@ -412,7 +407,7 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
}
}
}
} catch (e) {
} catch (e: any) {
errorNotificationToast(notifications, 'acknowledge', 'alerts', e);
}
if (successCount)
Expand All @@ -439,8 +434,8 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
endTime: DEFAULT_DATE_RANGE.end,
},
} = this.props;
const severities = new Set();
const statuses = new Set();
const severities = new Set<string>();
const statuses = new Set<string>();
filteredAlerts.forEach((alert) => {
if (alert) {
severities.add(alert.severity);
Expand Down Expand Up @@ -477,11 +472,10 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
],
};

const selection = {
const selection: EuiTableSelectionType<AlertItem> = {
onSelectionChange: this.onSelectionChange,
selectable: (item) => item.state === ALERT_STATE.ACTIVE,
selectableMessage: (selectable) =>
selectable ? undefined : DISABLE_ACKNOWLEDGED_ALERT_HELP_TEXT,
selectable: (item: AlertItem) => item.state === ALERT_STATE.ACTIVE,
selectableMessage: (selectable) => (selectable ? '' : DISABLE_ACKNOWLEDGED_ALERT_HELP_TEXT),
};

const sorting: any = {
Expand All @@ -500,7 +494,6 @@ export class Alerts extends Component<AlertsProps, AlertsState> {
detector={detectors[flyoutData.alertItem.detector_id]}
onClose={this.onFlyoutClose}
onAcknowledge={this.onAcknowledge}
findingsService={this.props.findingService}
indexPatternService={this.props.indexPatternService}
/>
)}
Expand Down
Loading

0 comments on commit a683e9c

Please sign in to comment.