Skip to content

Commit

Permalink
[Security Solution] [Detections] Adds support for system actions (and…
Browse files Browse the repository at this point in the history
… cases action) to detection rules (#183937)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 26, 2024
1 parent 1aee15f commit d749305
Show file tree
Hide file tree
Showing 54 changed files with 1,302 additions and 128 deletions.
12 changes: 10 additions & 2 deletions x-pack/plugins/cases/server/connectors/cases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
* 2.0.
*/

import { AlertingConnectorFeatureId, UptimeConnectorFeatureId } from '@kbn/actions-plugin/common';
import {
AlertingConnectorFeatureId,
UptimeConnectorFeatureId,
SecurityConnectorFeatureId,
} from '@kbn/actions-plugin/common';
import type { SubActionConnectorType } from '@kbn/actions-plugin/server/sub_action_framework/types';
import type { KibanaRequest } from '@kbn/core-http-server';
import type { SavedObjectsClientContract } from '@kbn/core/server';
Expand Down Expand Up @@ -56,7 +60,11 @@ export const getCasesConnectorType = ({
config: CasesConnectorConfigSchema,
secrets: CasesConnectorSecretsSchema,
},
supportedFeatureIds: [UptimeConnectorFeatureId, AlertingConnectorFeatureId],
supportedFeatureIds: [
UptimeConnectorFeatureId,
AlertingConnectorFeatureId,
SecurityConnectorFeatureId,
],
minimumLicenseRequired: 'platinum' as const,
isSystemActionType: true,
getKibanaPrivileges: ({ params } = { params: { subAction: 'run', subActionParams: {} } }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ export const RuleAction = z.object({
* The action type used for sending notifications.
*/
action_type_id: z.string(),
group: RuleActionGroup,
group: RuleActionGroup.optional(),
id: RuleActionId,
params: RuleActionParams,
uuid: NonEmptyString.optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,6 @@ components:
$ref: '#/components/schemas/RuleActionFrequency'
required:
- action_type_id
- group
- id
- params

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,17 +719,6 @@ describe('rules schema', () => {
);
});

test('You cannot send in an array of actions that are missing "group"', () => {
const payload = {
...getCreateRulesSchemaMock(),
actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }],
};

const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toEqual('actions.0.group: Required');
});

test('You cannot send in an array of actions that are missing "id"', () => {
const payload = {
...getCreateRulesSchemaMock(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export const BulkActionEditTypeEnum = BulkActionEditType.enum;
export type NormalizedRuleAction = z.infer<typeof NormalizedRuleAction>;
export const NormalizedRuleAction = z
.object({
group: RuleActionGroup,
group: RuleActionGroup.optional(),
id: RuleActionId,
params: RuleActionParams,
frequency: RuleActionFrequency.optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ components:
alerts_filter:
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionAlertsFilter'
required:
- group
- id
- params
additionalProperties: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -766,19 +766,6 @@ describe('Patch rule request schema', () => {
});
});

test('You cannot send in an array of actions that are missing "group"', () => {
const payload: Omit<PatchRuleRequestBody['actions'], 'group'> = {
...getPatchRulesSchemaMock(),
actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }],
};

const result = PatchRuleRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"actions.0.group: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.group: Required, actions.0.group: Required, and 12 more"`
);
});

test('You cannot send in an array of actions that are missing "id"', () => {
const payload: Omit<PatchRuleRequestBody['actions'], 'id'> = {
...getPatchRulesSchemaMock(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -776,20 +776,6 @@ describe('RuleToImport', () => {
expectParseSuccess(result);
});

test('You cannot send in an array of actions that are missing "group"', () => {
const payload = getImportRulesSchemaMock({
actions: [
// @ts-expect-error assign unsupported value
{ id: 'id', action_type_id: 'action_type_id', params: {} },
],
});

const result = RuleToImport.safeParse(payload);
expectParseError(result);

expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"actions.0.group: Required"`);
});

test('You cannot send in an array of actions that are missing "id"', () => {
const payload = getImportRulesSchemaMock({
actions: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
* 2.0.
*/

import type { RuleAction as AlertingRuleAction } from '@kbn/alerting-plugin/common';
import type { NormalizedAlertAction } from '@kbn/alerting-plugin/server/rules_client';
import type {
RuleAction as AlertingRuleAction,
RuleSystemAction as AlertingRuleSystemAction,
} from '@kbn/alerting-plugin/common';
import type {
NormalizedAlertAction,
NormalizedSystemAction,
} from '@kbn/alerting-plugin/server/rules_client';
import type { NormalizedRuleAction } from '../api/detection_engine/rule_management';
import type {
ResponseAction,
Expand All @@ -23,8 +29,7 @@ export const transformRuleToAlertAction = ({
uuid,
frequency,
alerts_filter: alertsFilter,
}: RuleAction): AlertingRuleAction => ({
group,
}: RuleAction): AlertingRuleAction | AlertingRuleSystemAction => ({
id,
params: params as AlertingRuleAction['params'],
actionTypeId,
Expand All @@ -33,6 +38,7 @@ export const transformRuleToAlertAction = ({
}),
...(uuid && { uuid }),
...(frequency && { frequency }),
...(group && { group }),
});

export const transformAlertToRuleAction = ({
Expand All @@ -44,13 +50,25 @@ export const transformAlertToRuleAction = ({
frequency,
alertsFilter,
}: AlertingRuleAction): RuleAction => ({
group,
id,
params,
action_type_id: actionTypeId,
...(alertsFilter && { alerts_filter: alertsFilter }),
...(uuid && { uuid }),
...(frequency && { frequency }),
...(group && { group }),
});

export const transformAlertToRuleSystemAction = ({
id,
actionTypeId,
params,
uuid,
}: AlertingRuleSystemAction): RuleAction => ({
id,
params,
action_type_id: actionTypeId,
...(uuid && { uuid }),
});

export const transformNormalizedRuleToAlertAction = ({
Expand All @@ -59,8 +77,7 @@ export const transformNormalizedRuleToAlertAction = ({
params,
frequency,
alerts_filter: alertsFilter,
}: NormalizedRuleAction): NormalizedAlertAction => ({
group,
}: NormalizedRuleAction): NormalizedAlertAction | NormalizedSystemAction => ({
id,
params: params as AlertingRuleAction['params'],
...(alertsFilter && {
Expand All @@ -70,6 +87,7 @@ export const transformNormalizedRuleToAlertAction = ({
alertsFilter: alertsFilter as AlertingRuleAction['alertsFilter'],
}),
...(frequency && { frequency }),
...(group && { group }),
});

export const transformAlertToNormalizedRuleAction = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4104,7 +4104,6 @@ components:
params:
$ref: '#/components/schemas/RuleActionParams'
required:
- group
- id
- params
NormalizedRuleError:
Expand Down Expand Up @@ -4902,7 +4901,6 @@ components:
$ref: '#/components/schemas/NonEmptyString'
required:
- action_type_id
- group
- id
- params
RuleActionAlertsFilter:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3272,7 +3272,6 @@ components:
params:
$ref: '#/components/schemas/RuleActionParams'
required:
- group
- id
- params
NormalizedRuleError:
Expand Down Expand Up @@ -4070,7 +4069,6 @@ components:
$ref: '#/components/schemas/NonEmptyString'
required:
- action_type_id
- group
- id
- params
RuleActionAlertsFilter:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ import React from 'react';
import { EuiToolTip, EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import type { ActionType, AsApiContract } from '@kbn/actions-plugin/common';
import type { ActionResult } from '@kbn/actions-plugin/server';
import type { RuleActionFrequency, RuleAction } from '@kbn/alerting-plugin/common';
import type {
RuleActionFrequency,
RuleAction,
RuleSystemAction,
} from '@kbn/alerting-plugin/common';
import type { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { getTimeTypeValue } from '../../../rule_creation_ui/pages/rule_creation/helpers';
import {
getTimeTypeValue,
isRuleAction as getIsRuleAction,
} from '../../../rule_creation_ui/pages/rule_creation/helpers';
import * as i18n from './translations';

const DescriptionLine = ({ children }: { children: React.ReactNode }) => (
Expand Down Expand Up @@ -79,7 +86,7 @@ export const FrequencyDescription: React.FC<{ frequency?: RuleActionFrequency }>
};

interface NotificationActionProps {
action: RuleAction;
action: RuleAction | RuleSystemAction;
connectorTypes: ActionType[];
connectors: Array<AsApiContract<ActionResult>>;
actionTypeRegistry: ActionTypeRegistryContract;
Expand All @@ -91,13 +98,23 @@ export function NotificationAction({
connectors,
actionTypeRegistry,
}: NotificationActionProps) {
const isRuleAction = getIsRuleAction(action, actionTypeRegistry);
const connectorType = connectorTypes.find(({ id }) => id === action.actionTypeId);
const connectorTypeName = connectorType?.name ?? '';
const registeredAction = actionTypeRegistry.get(action.actionTypeId);

const connector = connectors.find(({ id }) => id === action.id);
const connectorName = connector?.name ?? '';
/*
since there is no "connector" for system actions,
we need to determine the title based off the action
properties in order to render helpful text on the
rule details page.
*/
const connectorTypeName = isRuleAction
? connectorType?.name ?? ''
: registeredAction.actionTypeTitle ?? '';
const iconType = registeredAction?.iconClass ?? 'apps';

const iconType = actionTypeRegistry.get(action.actionTypeId)?.iconClass ?? 'apps';
const connector = connectors.find(({ id }) => id === action.id);
const connectorName = (isRuleAction ? connector?.name : registeredAction.actionTypeTitle) ?? '';

return (
<EuiFlexItem>
Expand All @@ -114,7 +131,13 @@ export function NotificationAction({
<EuiFlexItem grow={false}>
<EuiIcon size="s" type="bell" color="subdued" />
</EuiFlexItem>
<FrequencyDescription frequency={action.frequency} />
{isRuleAction ? (
<FrequencyDescription frequency={action.frequency} />
) : (
// Display frequency description for system action
// same text used by stack alerting
<DescriptionLine>{i18n.SYSTEM_ACTION_FREQUENCY}</DescriptionLine>
)}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ export const PERIODICALLY = i18n.translate(
'xpack.securitySolution.detectionEngine.actionNotifyWhen.periodically',
{ defaultMessage: 'Periodically' }
);

export const SYSTEM_ACTION_FREQUENCY = i18n.translate(
'xpack.securitySolution.detectionEngine.actionNotifyWhen.systemActionFrequency',
{ defaultMessage: 'On check intervals' }
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
ScheduleStepRule,
TimeframePreviewOptions,
} from '../../../../detections/pages/detection_engine/rules/types';
import { useKibana } from '../../../../common/lib/kibana';

interface PreviewRouteParams {
defineRuleData?: DefineStepRule;
Expand All @@ -34,6 +35,10 @@ export const usePreviewRoute = ({
}: PreviewRouteParams) => {
const [isRequestTriggered, setIsRequestTriggered] = useState(false);

const {
triggersActionsUi: { actionTypeRegistry },
} = useKibana().services;

const { isLoading, response, rule, setRule } = usePreviewRule({
timeframeOptions,
});
Expand Down Expand Up @@ -72,6 +77,7 @@ export const usePreviewRoute = ({
defineRuleData,
aboutRuleData,
scheduleRuleData,
actionTypeRegistry,
exceptionsList,
})
);
Expand All @@ -84,6 +90,7 @@ export const usePreviewRoute = ({
aboutRuleData,
scheduleRuleData,
exceptionsList,
actionTypeRegistry,
]);

return {
Expand Down
Loading

0 comments on commit d749305

Please sign in to comment.