diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 5d1f757267299..aade8be4f503f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -325,7 +325,7 @@ export const job_status = t.keyof({ succeeded: null, failed: null, 'going to run': null, - 'partial failure': null, + warning: null, }); export type JobStatus = t.TypeOf; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts index 4ed971ea6a936..cca745659d2cc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts @@ -14,6 +14,9 @@ export const getStatusColor = (status: RuleStatusType | string | null) => ? 'success' : status === 'failed' ? 'danger' - : status === 'executing' || status === 'going to run' || status === 'partial failure' + : status === 'executing' || + status === 'going to run' || + status === 'partial failure' || + status === 'warning' ? 'warning' : 'subdued'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx index 6292cc5b530b0..677e6de0ff485 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx @@ -16,7 +16,11 @@ import { import React, { memo, useCallback, useEffect, useState } from 'react'; import deepEqual from 'fast-deep-equal'; -import { useRuleStatus, RuleInfoStatus } from '../../../containers/detection_engine/rules'; +import { + useRuleStatus, + RuleInfoStatus, + RuleStatusType, +} from '../../../containers/detection_engine/rules'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { getStatusColor } from './helpers'; @@ -55,6 +59,19 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) } }, [fetchRuleStatus, ruleId]); + const getStatus = useCallback((status: RuleStatusType | null | undefined) => { + if (status == null) { + return getEmptyTagValue(); + } else if (status != null && status === 'partial failure') { + // Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning' + // On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status + // and this code will no longer be necessary + // TODO: remove this code in 8.0.0 + return 'warning'; + } + return status; + }, []); + return ( @@ -71,7 +88,7 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) - {currentStatus?.status ?? getEmptyTagValue()} + {getStatus(currentStatus?.status)} diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index b703bc3aa8bcb..b8f6c4bde3e8f 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -78,6 +78,7 @@ const StatusTypes = t.union([ t.literal('failed'), t.literal('going to run'), t.literal('partial failure'), + t.literal('warning'), ]); // TODO: make a ticket @@ -254,7 +255,13 @@ export interface RuleStatus { failures: RuleInfoStatus[]; } -export type RuleStatusType = 'executing' | 'failed' | 'going to run' | 'succeeded'; +export type RuleStatusType = + | 'executing' + | 'failed' + | 'going to run' + | 'succeeded' + | 'partial failure' + | 'warning'; export interface RuleInfoStatus { alert_id: string; status_date: string; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index 425848cd09af0..d110f2d52b3c5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -313,7 +313,11 @@ export const getMonitoringColumns = ( }} href={formatUrl(getRuleDetailsUrl(item.id))} > - {value} + {/* Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning' */} + {/* On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status */} + {/* and this code will no longer be necessary */} + {/* TODO: remove this code in 8.0.0 */} + {value === 'partial failure' ? 'warning' : value} ); }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 5836cac09e9b8..ed88ca41146f1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -314,7 +314,7 @@ const RuleDetailsPageComponent = () => { /> ); } else if ( - rule?.status === 'partial failure' && + (rule?.status === 'warning' || rule?.status === 'partial failure') && ruleDetailTab === RuleDetailTabs.alerts && rule?.last_success_at != null ) { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts index 4e6d8f4d567b1..1d100fb9109d0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts @@ -52,7 +52,7 @@ export const ERROR_CALLOUT_TITLE = i18n.translate( export const PARTIAL_FAILURE_CALLOUT_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.partialErrorCalloutTitle', { - defaultMessage: 'Partial rule failure at', + defaultMessage: 'Warning at', } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts index 04f2b6ff799da..5893b05a1d811 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts @@ -23,7 +23,7 @@ export const ruleStatusServiceFactoryMock = async ({ success: jest.fn(), - partialFailure: jest.fn(), + warning: jest.fn(), error: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts index f60591422e1ee..7f2962ae0a6c8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts @@ -54,13 +54,13 @@ describe('buildRuleStatusAttributes', () => { expect(result.statusDate).toEqual(result.lastSuccessAt); }); - it('returns partial failure fields if "partial failure"', () => { + it('returns warning fields if "warning"', () => { const result = buildRuleStatusAttributes( - 'partial failure', + 'warning', 'some indices missing timestamp override field' ); expect(result).toEqual({ - status: 'partial failure', + status: 'warning', statusDate: expectIsoDateString, lastSuccessAt: expectIsoDateString, lastSuccessMessage: 'some indices missing timestamp override field', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts index f4abf9aa5ced8..6e93ed256321e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts @@ -24,7 +24,7 @@ interface Attributes { export interface RuleStatusService { goingToRun: () => Promise; success: (message: string, attributes?: Attributes) => Promise; - partialFailure: (message: string, attributes?: Attributes) => Promise; + warning: (message: string, attributes?: Attributes) => Promise; error: (message: string, attributes?: Attributes) => Promise; } @@ -48,7 +48,7 @@ export const buildRuleStatusAttributes: ( lastSuccessMessage: message, }; } - case 'partial failure': { + case 'warning': { return { ...baseAttributes, lastSuccessAt: now, @@ -102,7 +102,7 @@ export const ruleStatusServiceFactory = async ({ }); }, - partialFailure: async (message, attributes) => { + warning: async (message, attributes) => { const [currentStatus] = await getOrCreateRuleStatuses({ alertId, ruleStatusClient, @@ -110,7 +110,7 @@ export const ruleStatusServiceFactory = async ({ await ruleStatusClient.update(currentStatus.id, { ...currentStatus.attributes, - ...buildRuleStatusAttributes('partial failure', message, attributes), + ...buildRuleStatusAttributes('warning', message, attributes), }); }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 02a0582e540f4..a79961eb716fd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -110,7 +110,7 @@ describe('rules_notification_alert_type', () => { find: jest.fn(), goingToRun: jest.fn(), error: jest.fn(), - partialFailure: jest.fn(), + warning: jest.fn(), }; (ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService); (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0)); @@ -207,7 +207,7 @@ describe('rules_notification_alert_type', () => { }); }); - it('should set a partial failure for when rules cannot read ALL provided indices', async () => { + it('should set a warning for when rules cannot read ALL provided indices', async () => { (checkPrivileges as jest.Mock).mockResolvedValueOnce({ username: 'elastic', has_all_requested: false, @@ -227,8 +227,8 @@ describe('rules_notification_alert_type', () => { }); payload.params.index = ['some*', 'myfa*', 'anotherindex*']; await alert.executor(payload); - expect(ruleStatusService.partialFailure).toHaveBeenCalled(); - expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain( + expect(ruleStatusService.warning).toHaveBeenCalled(); + expect(ruleStatusService.warning.mock.calls[0][0]).toContain( 'Missing required read privileges on the following indices: ["some*"]' ); }); @@ -250,8 +250,8 @@ describe('rules_notification_alert_type', () => { }); payload.params.index = ['some*', 'myfa*']; await alert.executor(payload); - expect(ruleStatusService.partialFailure).toHaveBeenCalled(); - expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain( + expect(ruleStatusService.warning).toHaveBeenCalled(); + expect(ruleStatusService.warning.mock.calls[0][0]).toContain( 'This rule may not have the required read privileges to the following indices: ["myfa*","some*"]' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 8e63633cd49fd..ecb36a8b050d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -181,7 +181,7 @@ export const signalRulesAlertType = ({ logger.debug(buildRuleMessage('[+] Starting Signal Rule execution')); logger.debug(buildRuleMessage(`interval: ${interval}`)); - let wrotePartialFailureStatus = false; + let wroteWarningStatus = false; await ruleStatusService.goingToRun(); // check if rule has permissions to access given index pattern @@ -202,7 +202,7 @@ export const signalRulesAlertType = ({ }), ]); - wrotePartialFailureStatus = await flow( + wroteWarningStatus = await flow( () => tryCatch( () => @@ -659,7 +659,7 @@ export const signalRulesAlertType = ({ `[+] Finished indexing ${result.createdSignalsCount} signals into ${outputIndex}` ) ); - if (!hasError && !wrotePartialFailureStatus) { + if (!hasError && !wroteWarningStatus) { await ruleStatusService.success('succeeded', { bulkCreateTimeDurations: result.bulkCreateTimes, searchAfterTimeDurations: result.searchAfterTimes, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 75bd9f593a6ac..f7e1eb7622779 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -69,7 +69,7 @@ const ruleStatusServiceMock = { find: jest.fn(), goingToRun: jest.fn(), error: jest.fn(), - partialFailure: jest.fn(), + warning: jest.fn(), }; describe('utils', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 72e5bc0c5b879..f6bd5c8a325be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -79,12 +79,12 @@ export const hasReadIndexPrivileges = async ( if (indexesWithReadPrivileges.length > 0 && indexesWithNoReadPrivileges.length > 0) { // some indices have read privileges others do not. - // set a partial failure status + // set a warning status const errorString = `Missing required read privileges on the following indices: ${JSON.stringify( indexesWithNoReadPrivileges )}`; logger.error(buildRuleMessage(errorString)); - await ruleStatusService.partialFailure(errorString); + await ruleStatusService.warning(errorString); return true; } else if ( indexesWithReadPrivileges.length === 0 && @@ -96,7 +96,7 @@ export const hasReadIndexPrivileges = async ( indexesWithNoReadPrivileges )}`; logger.error(buildRuleMessage(errorString)); - await ruleStatusService.partialFailure(errorString); + await ruleStatusService.warning(errorString); return true; } return false; @@ -119,7 +119,7 @@ export const hasTimestampFields = async ( inputIndices )}`; logger.error(buildRuleMessage(errorString)); - await ruleStatusService.error(errorString); + await ruleStatusService.warning(errorString); return true; } else if ( !wroteStatus && @@ -128,7 +128,7 @@ export const hasTimestampFields = async ( timestampFieldCapsResponse.body.fields[timestampField]?.unmapped?.indices != null) ) { // if there is a timestamp override and the unmapped array for the timestamp override key is not empty, - // partial failure + // warning const errorString = `The following indices are missing the ${ timestampField === '@timestamp' ? 'timestamp field "@timestamp"' @@ -139,7 +139,7 @@ export const hasTimestampFields = async ( : timestampFieldCapsResponse.body.fields[timestampField].unmapped.indices )}`; logger.error(buildRuleMessage(errorString)); - await ruleStatusService.partialFailure(errorString); + await ruleStatusService.warning(errorString); return true; } return wroteStatus; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index 1cef8570d4f23..a9120bde274f2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -118,7 +118,7 @@ export default ({ getService }: FtrProviderContext) => { expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); - it('should create a single rule with a rule_id and an index pattern that does not match anything available and fail the rule', async () => { + it('should create a single rule with a rule_id and an index pattern that does not match anything available and warning for the rule', async () => { const simpleRule = getRuleForSignalTesting(['does-not-exist-*']); const { body } = await supertest .post(DETECTION_ENGINE_RULES_URL) @@ -126,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => { .send(simpleRule) .expect(200); - await waitForRuleSuccessOrStatus(supertest, body.id, 'failed'); + await waitForRuleSuccessOrStatus(supertest, body.id, 'warning'); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) @@ -134,8 +134,8 @@ export default ({ getService }: FtrProviderContext) => { .send({ ids: [body.id] }) .expect(200); - expect(statusBody[body.id].current_status.status).to.eql('failed'); - expect(statusBody[body.id].current_status.last_failure_message).to.eql( + expect(statusBody[body.id].current_status.status).to.eql('warning'); + expect(statusBody[body.id].current_status.last_success_message).to.eql( 'The following index patterns did not match any indices: ["does-not-exist-*"]' ); }); @@ -287,10 +287,7 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllAlerts(supertest); await esArchiver.unload('security_solution/timestamp_override'); }); - it.skip('should create a single rule which has a timestamp override and generates two signals with a failing status', async () => { - // should be a failing status because one of the indices in the index pattern is missing - // the timestamp override field. - + it('should create a single rule which has a timestamp override and generates two signals with a "warning" status', async () => { // defaults to event.ingested timestamp override. // event.ingested is one of the timestamp fields set on the es archive data // inside of x-pack/test/functional/es_archives/security_solution/timestamp_override/data.json.gz @@ -302,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyId = body.id; - await waitForRuleSuccessOrStatus(supertest, bodyId, 'partial failure'); + await waitForRuleSuccessOrStatus(supertest, bodyId, 'warning'); await waitForSignalsToBePresent(supertest, 2, [bodyId]); const { body: statusBody } = await supertest @@ -311,9 +308,7 @@ export default ({ getService }: FtrProviderContext) => { .send({ ids: [bodyId] }) .expect(200); - // set to "failed" for now. Will update this with a partial failure - // once I figure out the logic - expect(statusBody[bodyId].current_status.status).to.eql('partial failure'); + expect(statusBody[bodyId].current_status.status).to.eql('warning'); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 158247ee244dd..4875e837556fc 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -972,7 +972,7 @@ export const getRule = async ( export const waitForRuleSuccessOrStatus = async ( supertest: SuperTest, id: string, - status: 'succeeded' | 'failed' | 'partial failure' = 'succeeded' + status: 'succeeded' | 'failed' | 'partial failure' | 'warning' = 'succeeded' ): Promise => { await waitFor(async () => { const { body } = await supertest