diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index 016c77d7254dd..d86a04f343a89 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -359,6 +359,11 @@ import type { GetRuleMigrationResourcesResponse, GetRuleMigrationStatsRequestParamsInput, GetRuleMigrationStatsResponse, + InstallMigrationRulesRequestParamsInput, + InstallMigrationRulesRequestBodyInput, + InstallMigrationRulesResponse, + InstallTranslatedMigrationRulesRequestParamsInput, + InstallTranslatedMigrationRulesResponse, StartRuleMigrationRequestParamsInput, StartRuleMigrationRequestBodyInput, StartRuleMigrationResponse, @@ -1559,6 +1564,22 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Installs migration rules + */ + async installMigrationRules(props: InstallMigrationRulesProps) { + this.log.info(`${new Date().toISOString()} Calling API InstallMigrationRules`); + return this.kbnClient + .request({ + path: replaceParams('/internal/siem_migrations/rules/{migration_id}/install', props.params), + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + }, + method: 'POST', + body: props.body, + }) + .catch(catchAxiosErrorFormatAndThrow); + } /** * Install and update all Elastic prebuilt detection rules and Timelines. */ @@ -1590,6 +1611,24 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Installs all translated migration rules + */ + async installTranslatedMigrationRules(props: InstallTranslatedMigrationRulesProps) { + this.log.info(`${new Date().toISOString()} Calling API InstallTranslatedMigrationRules`); + return this.kbnClient + .request({ + path: replaceParams( + '/internal/siem_migrations/rules/{migration_id}/install_translated', + props.params + ), + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + }, + method: 'POST', + }) + .catch(catchAxiosErrorFormatAndThrow); + } async internalUploadAssetCriticalityRecords(props: InternalUploadAssetCriticalityRecordsProps) { this.log.info(`${new Date().toISOString()} Calling API InternalUploadAssetCriticalityRecords`); return this.kbnClient @@ -2324,9 +2363,16 @@ export interface InitEntityEngineProps { export interface InitEntityStoreProps { body: InitEntityStoreRequestBodyInput; } +export interface InstallMigrationRulesProps { + params: InstallMigrationRulesRequestParamsInput; + body: InstallMigrationRulesRequestBodyInput; +} export interface InstallPrepackedTimelinesProps { body: InstallPrepackedTimelinesRequestBodyInput; } +export interface InstallTranslatedMigrationRulesProps { + params: InstallTranslatedMigrationRulesRequestParamsInput; +} export interface InternalUploadAssetCriticalityRecordsProps { attachment: FormData; } diff --git a/x-pack/plugins/security_solution/common/siem_migrations/constants.ts b/x-pack/plugins/security_solution/common/siem_migrations/constants.ts index 5e6c5edbfd3fc..565091e39a8db 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/constants.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; + export const SIEM_MIGRATIONS_PATH = '/internal/siem_migrations' as const; export const SIEM_RULE_MIGRATIONS_PATH = `${SIEM_MIGRATIONS_PATH}/rules` as const; @@ -14,6 +16,9 @@ export const SIEM_RULE_MIGRATION_START_PATH = `${SIEM_RULE_MIGRATION_PATH}/start export const SIEM_RULE_MIGRATION_RETRY_PATH = `${SIEM_RULE_MIGRATION_PATH}/retry` as const; export const SIEM_RULE_MIGRATION_STATS_PATH = `${SIEM_RULE_MIGRATION_PATH}/stats` as const; export const SIEM_RULE_MIGRATION_STOP_PATH = `${SIEM_RULE_MIGRATION_PATH}/stop` as const; +export const SIEM_RULE_MIGRATION_INSTALL_PATH = `${SIEM_RULE_MIGRATION_PATH}/install` as const; +export const SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH = + `${SIEM_RULE_MIGRATION_PATH}/install_translated` as const; export const SIEM_RULE_MIGRATION_RESOURCES_PATH = `${SIEM_RULE_MIGRATION_PATH}/resources` as const; @@ -36,3 +41,6 @@ export enum SiemMigrationRuleTranslationResult { PARTIAL = 'partial', UNTRANSLATABLE = 'untranslatable', } + +export const DEFAULT_TRANSLATION_RISK_SCORE = 21; +export const DEFAULT_TRANSLATION_SEVERITY: Severity = 'low'; diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts index 463ec97dd200e..77a0fc94408f9 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts @@ -88,6 +88,48 @@ export type GetRuleMigrationStatsRequestParamsInput = z.input< export type GetRuleMigrationStatsResponse = z.infer; export const GetRuleMigrationStatsResponse = RuleMigrationTaskStats; +export type InstallMigrationRulesRequestParams = z.infer; +export const InstallMigrationRulesRequestParams = z.object({ + migration_id: NonEmptyString, +}); +export type InstallMigrationRulesRequestParamsInput = z.input< + typeof InstallMigrationRulesRequestParams +>; + +export type InstallMigrationRulesRequestBody = z.infer; +export const InstallMigrationRulesRequestBody = z.array(NonEmptyString); +export type InstallMigrationRulesRequestBodyInput = z.input< + typeof InstallMigrationRulesRequestBody +>; + +export type InstallMigrationRulesResponse = z.infer; +export const InstallMigrationRulesResponse = z.object({ + /** + * Indicates rules migrations have been installed. + */ + installed: z.boolean(), +}); + +export type InstallTranslatedMigrationRulesRequestParams = z.infer< + typeof InstallTranslatedMigrationRulesRequestParams +>; +export const InstallTranslatedMigrationRulesRequestParams = z.object({ + migration_id: NonEmptyString, +}); +export type InstallTranslatedMigrationRulesRequestParamsInput = z.input< + typeof InstallTranslatedMigrationRulesRequestParams +>; + +export type InstallTranslatedMigrationRulesResponse = z.infer< + typeof InstallTranslatedMigrationRulesResponse +>; +export const InstallTranslatedMigrationRulesResponse = z.object({ + /** + * Indicates rules migrations have been installed. + */ + installed: z.boolean(), +}); + export type StartRuleMigrationRequestParams = z.infer; export const StartRuleMigrationRequestParams = z.object({ migration_id: NonEmptyString, diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml index 6e713e498f6be..f57a809bb204e 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml @@ -81,6 +81,73 @@ paths: type: boolean description: Indicates rules migrations have been updated. + /internal/siem_migrations/rules/{migration_id}/install: + post: + summary: Installs translated migration rules + operationId: InstallMigrationRules + x-codegen-enabled: true + description: Installs migration rules + tags: + - SIEM Rule Migrations + parameters: + - name: migration_id + in: path + required: true + schema: + description: The migration id to isnstall rules for + $ref: '../../common.schema.yaml#/components/schemas/NonEmptyString' + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + description: The rule migration id + $ref: '../../common.schema.yaml#/components/schemas/NonEmptyString' + responses: + 200: + description: Indicates rules migrations have been installed correctly. + content: + application/json: + schema: + type: object + required: + - installed + properties: + installed: + type: boolean + description: Indicates rules migrations have been installed. + + /internal/siem_migrations/rules/{migration_id}/install_translated: + post: + summary: Installs all translated migration rules + operationId: InstallTranslatedMigrationRules + x-codegen-enabled: true + description: Installs all translated migration rules + tags: + - SIEM Rule Migrations + parameters: + - name: migration_id + in: path + required: true + schema: + description: The migration id to install translated rules for + $ref: '../../common.schema.yaml#/components/schemas/NonEmptyString' + responses: + 200: + description: Indicates rules migrations have been installed correctly. + content: + application/json: + schema: + type: object + required: + - installed + properties: + installed: + type: boolean + description: Indicates rules migrations have been installed. + /internal/siem_migrations/rules/stats: get: summary: Retrieves the stats for all rule migrations diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts index f953a53c281f5..3b7605e032259 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts @@ -11,14 +11,19 @@ import { KibanaServices } from '../../../common/lib/kibana'; import { SIEM_RULE_MIGRATIONS_ALL_STATS_PATH, + SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, + SIEM_RULE_MIGRATION_INSTALL_PATH, SIEM_RULE_MIGRATION_PATH, SIEM_RULE_MIGRATION_START_PATH, } from '../../../../common/siem_migrations/constants'; import type { GetAllStatsRuleMigrationResponse, GetRuleMigrationResponse, + InstallTranslatedMigrationRulesResponse, + InstallMigrationRulesResponse, StartRuleMigrationRequestBody, } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import type { InstallTranslatedRulesProps, InstallRulesProps } from '../types'; /** * Retrieves the stats for all the existing migrations, aggregated by `migration_id`. @@ -82,3 +87,33 @@ export const getRuleMigrations = async ({ { method: 'GET', version: '1', signal } ); }; + +export const installMigrationRules = async ({ + migrationId, + ids, + signal, +}: InstallRulesProps): Promise => { + return KibanaServices.get().http.fetch( + replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }), + { + method: 'POST', + version: '1', + body: JSON.stringify(ids), + signal, + } + ); +}; + +export const installTranslatedMigrationRules = async ({ + migrationId, + signal, +}: InstallTranslatedRulesProps): Promise => { + return KibanaServices.get().http.fetch( + replaceParams(SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, { migration_id: migrationId }), + { + method: 'POST', + version: '1', + signal, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_rule_migrations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts similarity index 58% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_rule_migrations.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts index 76cf01c6c35d0..fece8f8c3ca07 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_rule_migrations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts @@ -6,14 +6,15 @@ */ import type { UseQueryOptions } from '@tanstack/react-query'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { replaceParams } from '@kbn/openapi-common/shared'; +import { useCallback } from 'react'; import { DEFAULT_QUERY_OPTIONS } from './constants'; import { getRuleMigrations } from '../api'; import type { GetRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; import { SIEM_RULE_MIGRATION_PATH } from '../../../../../common/siem_migrations/constants'; -export const useGetRuleMigrationsQuery = ( +export const useGetMigrationRulesQuery = ( migrationId: string, options?: UseQueryOptions ) => { @@ -31,3 +32,23 @@ export const useGetRuleMigrationsQuery = ( } ); }; + +/** + * We should use this hook to invalidate the rule migrations cache. For + * example, rule migrations mutations, like installing a rule, should lead to cache invalidation. + * + * @returns A rule migrations cache invalidation callback + */ +export const useInvalidateGetMigrationRulesQuery = (migrationId: string) => { + const queryClient = useQueryClient(); + + const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, { + migration_id: migrationId, + }); + + return useCallback(() => { + queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_PATH], { + refetchType: 'active', + }); + }, [SPECIFIC_MIGRATION_PATH, queryClient]); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_all_migration_rules_mutation.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_all_migration_rules_mutation.ts new file mode 100644 index 0000000000000..f946dc165450f --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_all_migration_rules_mutation.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import type { InstallTranslatedMigrationRulesResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../../common/siem_migrations/constants'; +import { installTranslatedMigrationRules } from '../api'; +import { useInvalidateGetMigrationRulesQuery } from './use_get_migration_rules_query'; + +export const INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY = [ + 'POST', + SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, +]; + +export const useInstallAllMigrationRulesMutation = ( + migrationId: string, + options?: UseMutationOptions +) => { + const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId); + + return useMutation( + () => installTranslatedMigrationRules({ migrationId }), + { + ...options, + mutationKey: INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY, + onSettled: (...args) => { + invalidateGetRuleMigrationsQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts new file mode 100644 index 0000000000000..6aaff55e24513 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import type { InstallMigrationRulesResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../../common/siem_migrations/constants'; +import { installMigrationRules } from '../api'; +import { useInvalidateGetMigrationRulesQuery } from './use_get_migration_rules_query'; + +export const INSTALL_MIGRATION_RULES_MUTATION_KEY = ['POST', SIEM_RULE_MIGRATION_INSTALL_PATH]; + +export const useInstallMigrationRulesMutation = ( + migrationId: string, + options?: UseMutationOptions +) => { + const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId); + + return useMutation( + (ids: string[]) => installMigrationRules({ migrationId, ids }), + { + ...options, + mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY, + onSettled: (...args) => { + invalidateGetRuleMigrationsQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx new file mode 100644 index 0000000000000..df6d01d876fce --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import * as i18n from './translations'; + +export interface BulkActionsProps { + isTableLoading: boolean; + numberOfTranslatedRules: number; + numberOfSelectedRules: number; + installTranslatedRule?: () => void; + installSelectedRule?: () => void; +} + +/** + * Collection of buttons to perform bulk actions on migration rules within the SIEM Rules Migrations table. + */ +export const BulkActions: React.FC = React.memo( + ({ + isTableLoading, + numberOfTranslatedRules, + numberOfSelectedRules, + installTranslatedRule, + installSelectedRule, + }) => { + const showInstallTranslatedRulesButton = numberOfTranslatedRules > 0; + const showInstallSelectedRulesButton = + showInstallTranslatedRulesButton && numberOfSelectedRules > 0; + return ( + + {showInstallSelectedRulesButton ? ( + + + {i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)} + {isTableLoading && } + + + ) : null} + {showInstallTranslatedRulesButton ? ( + + + {i18n.INSTALL_ALL_RULES(numberOfTranslatedRules)} + {isTableLoading && } + + + ) : null} + + ); + } +); +BulkActions.displayName = 'BulkActions'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx index 5f4ae3098b6a3..25dffc64cccc5 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx @@ -8,15 +8,10 @@ import { EuiFlexGroup } from '@elastic/eui'; import type { Dispatch, SetStateAction } from 'react'; import React, { useCallback } from 'react'; -import styled from 'styled-components'; import * as i18n from './translations'; import { RuleSearchField } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rule_search_field'; import type { TableFilterOptions } from '../../hooks/use_filter_rules_to_install'; -const FilterWrapper = styled(EuiFlexGroup)` - margin-bottom: ${({ theme }) => theme.eui.euiSizeM}; -`; - export interface FiltersComponentProps { /** * Currently selected table filter @@ -45,13 +40,13 @@ const FiltersComponent: React.FC = ({ filterOptions, setF ); return ( - + - + ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx index 0cd3e07ea11a4..16f93a1cdebaf 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx @@ -13,8 +13,9 @@ import { EuiSkeletonText, EuiFlexGroup, EuiFlexItem, + EuiSpacer, } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import { @@ -24,32 +25,26 @@ import { import { NoItemsMessage } from './no_items_message'; import { Filters } from './filters'; import { useRulesTableColumns } from '../../hooks/use_rules_table_columns'; -import { useGetRuleMigrationsQuery } from '../../api/hooks/use_get_rule_migrations'; import type { TableFilterOptions } from '../../hooks/use_filter_rules_to_install'; import { useFilterRulesToInstall } from '../../hooks/use_filter_rules_to_install'; +import { useRulePreviewFlyout } from '../../hooks/use_rule_preview_flyout'; +import { useInstallMigrationRules } from '../../logic/use_install_migration_rules'; +import { useGetMigrationRules } from '../../logic/use_get_migration_rules'; +import { useInstallAllMigrationRules } from '../../logic/use_install_all_migration_rules'; +import { BulkActions } from './bulk_actions'; export interface RulesTableComponentProps { /** * Selected rule migration id */ migrationId: string; - - /** - * Opens the flyout with the details of the rule migration - * @param rule Rule migration - * @returns - */ - openRulePreview: (rule: RuleMigration) => void; } /** * Table Component for displaying SIEM rules migrations */ -const RulesTableComponent: React.FC = ({ - migrationId, - openRulePreview, -}) => { - const { data: ruleMigrations, isLoading } = useGetRuleMigrationsQuery(migrationId); +const RulesTableComponent: React.FC = ({ migrationId }) => { + const { data: ruleMigrations, isLoading: isDataLoading } = useGetMigrationRules(migrationId); const [selectedRuleMigrations, setSelectedRuleMigrations] = useState([]); @@ -62,10 +57,60 @@ const RulesTableComponent: React.FC = ({ ruleMigrations: ruleMigrations ?? [], }); - const shouldShowProgress = isLoading; + const { mutateAsync: installMigrationRules } = useInstallMigrationRules(migrationId); + const { mutateAsync: installAllMigrationRules } = useInstallAllMigrationRules(migrationId); + + const numberOfTranslatedRules = useMemo(() => { + return filteredRuleMigrations.filter( + (rule) => + !rule.elastic_rule?.id && + (rule.elastic_rule?.prebuilt_rule_id || rule.translation_result === 'full') + ).length; + }, [filteredRuleMigrations]); + + const [isTableLoading, setTableLoading] = useState(false); + const installSingleRule = useCallback( + async (migrationRule: RuleMigration, enable?: boolean) => { + setTableLoading(true); + try { + await installMigrationRules([migrationRule.id]); + } finally { + setTableLoading(false); + } + }, + [installMigrationRules] + ); + + const installTranslatedRules = useCallback( + async (enable?: boolean) => { + setTableLoading(true); + try { + await installAllMigrationRules(); + } finally { + setTableLoading(false); + } + }, + [installAllMigrationRules] + ); + + const ruleActionsFactory = useCallback( + (ruleMigration: RuleMigration, closeRulePreview: () => void) => { + // TODO: Add flyout action buttons + return null; + }, + [] + ); + + const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ + ruleActionsFactory, + }); + + const shouldShowProgress = isDataLoading; const rulesColumns = useRulesTableColumns({ - openRulePreview, + disableActions: isTableLoading, + openMigrationRulePreview: openRulePreview, + installMigrationRule: installSingleRule, }); return ( @@ -79,7 +124,7 @@ const RulesTableComponent: React.FC = ({ /> )} @@ -91,13 +136,22 @@ const RulesTableComponent: React.FC = ({ ) : ( <> - - + + + + + - + = ({ ) } /> + {rulePreviewFlyout} ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts index 812f26f628e49..3da9886659916 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts @@ -34,3 +34,32 @@ export const GO_BACK_TO_RULES_TABLE_BUTTON = i18n.translate( defaultMessage: 'Go back to SIEM Migrations', } ); + +export const INSTALL_SELECTED_RULES = (numberOfSelectedRules: number) => { + return i18n.translate('xpack.securitySolution.siemMigrations.rules.table.installSelectedRules', { + defaultMessage: 'Install selected ({numberOfSelectedRules})', + values: { numberOfSelectedRules }, + }); +}; + +export const INSTALL_ALL_RULES = (numberOfAllRules: number) => { + return i18n.translate('xpack.securitySolution.siemMigrations.rules.table.installAllRules', { + defaultMessage: + 'Install translated {numberOfAllRules, plural, one {rule} other {rules}} ({numberOfAllRules})', + values: { numberOfAllRules }, + }); +}; + +export const INSTALL_SELECTED_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.installSelectedButtonAriaLabel', + { + defaultMessage: 'Install selected translated rules', + } +); + +export const INSTALL_ALL_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.installAllButtonAriaLabel', + { + defaultMessage: 'Install all translated rules', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx new file mode 100644 index 0000000000000..7122949dee907 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { getRuleDetailsUrl } from '../../../../common/components/link_to'; +import { useKibana } from '../../../../common/lib/kibana'; +import { APP_UI_ID, SecurityPageName } from '../../../../../common'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import * as i18n from './translations'; +import type { TableColumn } from './constants'; + +interface ActionNameProps { + disableActions?: boolean; + migrationRule: RuleMigration; + openMigrationRulePreview: (migrationRule: RuleMigration) => void; + installMigrationRule: (migrationRule: RuleMigration, enable?: boolean) => void; +} + +const ActionName = ({ + disableActions, + migrationRule, + openMigrationRulePreview, + installMigrationRule, +}: ActionNameProps) => { + const { navigateToApp } = useKibana().services.application; + if (migrationRule.elastic_rule?.id) { + const ruleId = migrationRule.elastic_rule.id; + return ( + { + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(ruleId), + }); + }} + data-test-subj="viewRule" + > + {i18n.ACTIONS_VIEW_LABEL} + + ); + } + + if (migrationRule.status === 'failed') { + return ( + {}} data-test-subj="restartRule"> + {i18n.ACTIONS_RESTART_LABEL} + + ); + } + + if (migrationRule.translation_result === 'full') { + return ( + { + installMigrationRule(migrationRule); + }} + data-test-subj="installRule" + > + {i18n.ACTIONS_INSTALL_LABEL} + + ); + } + + return ( + { + openMigrationRulePreview(migrationRule); + }} + data-test-subj="editRule" + > + {i18n.ACTIONS_EDIT_LABEL} + + ); +}; + +interface CreateActionsColumnProps { + disableActions?: boolean; + openMigrationRulePreview: (migrationRule: RuleMigration) => void; + installMigrationRule: (migrationRule: RuleMigration, enable?: boolean) => void; +} + +export const createActionsColumn = ({ + disableActions, + openMigrationRulePreview, + installMigrationRule, +}: CreateActionsColumnProps): TableColumn => { + return { + field: 'elastic_rule', + name: i18n.COLUMN_ACTIONS, + render: (value: RuleMigration['elastic_rule'], migrationRule: RuleMigration) => { + return ( + + ); + }, + width: '10%', + align: 'center', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/constants.tsx similarity index 53% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/translations.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/constants.tsx index 74845b5f257ad..724e4dcb101a1 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/constants.tsx @@ -5,11 +5,7 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; +import type { EuiBasicTableColumn } from '@elastic/eui'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; -export const COLUMN_STATUS = i18n.translate( - 'xpack.securitySolution.siemMigrations.rules.columns.statusTitle', - { - defaultMessage: 'Status', - } -); +export type TableColumn = EuiBasicTableColumn; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/constants.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx similarity index 57% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/utils/constants.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx index 7400d4b0bcb63..a402e61a444af 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/constants.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx @@ -5,7 +5,10 @@ * 2.0. */ -import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +export * from './constants'; -export const DEFAULT_TRANSLATION_RISK_SCORE = 21; -export const DEFAULT_TRANSLATION_SEVERITY: Severity = 'low'; +export * from './actions'; +export * from './name'; +export * from './risk_score'; +export * from './severity'; +export * from './status'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx new file mode 100644 index 0000000000000..7b7cf228895fc --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink } from '@elastic/eui'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import * as i18n from './translations'; +import type { TableColumn } from './constants'; + +interface NameProps { + name: string; + rule: RuleMigration; + openMigrationRulePreview: (rule: RuleMigration) => void; +} + +const Name = ({ name, rule, openMigrationRulePreview }: NameProps) => { + return ( + { + openMigrationRulePreview(rule); + }} + data-test-subj="ruleName" + > + {name} + + ); +}; + +export const createNameColumn = ({ + openMigrationRulePreview, +}: { + openMigrationRulePreview: (rule: RuleMigration) => void; +}): TableColumn => { + return { + field: 'original_rule.title', + name: i18n.COLUMN_NAME, + render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => ( + + ), + sortable: true, + truncateText: true, + width: '40%', + align: 'left', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx new file mode 100644 index 0000000000000..e9eca65736a51 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiText } from '@elastic/eui'; +import { DEFAULT_TRANSLATION_RISK_SCORE } from '../../../../../common/siem_migrations/constants'; +import * as i18n from './translations'; +import type { TableColumn } from './constants'; + +export const createRiskScoreColumn = (): TableColumn => { + return { + field: 'risk_score', + name: i18n.COLUMN_RISK_SCORE, + render: () => ( + + {DEFAULT_TRANSLATION_RISK_SCORE} + + ), + sortable: true, + truncateText: true, + width: '75px', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx new file mode 100644 index 0000000000000..4ea737844ad53 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import { DEFAULT_TRANSLATION_SEVERITY } from '../../../../../common/siem_migrations/constants'; +import { getNormalizedSeverity } from '../../../../detection_engine/rule_management_ui/components/rules_table/helpers'; +import { SeverityBadge } from '../../../../common/components/severity_badge'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { TableColumn } from './constants'; +import * as i18n from './translations'; + +export const createSeverityColumn = (): TableColumn => { + return { + field: 'elastic_rule.severity', + name: i18n.COLUMN_SEVERITY, + render: (value?: Severity) => , + sortable: ({ elastic_rule: elasticRule }: RuleMigration) => + getNormalizedSeverity((elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY), + truncateText: true, + width: '12%', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx new file mode 100644 index 0000000000000..982f6ba7580b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import * as i18n from './translations'; +import type { TableColumn } from './constants'; +import { StatusBadge } from '../status_badge'; + +export const createStatusColumn = (): TableColumn => { + return { + field: 'translation_result', + name: i18n.COLUMN_STATUS, + render: (value: RuleMigration['translation_result'], rule: RuleMigration) => ( + + ), + sortable: false, + truncateText: true, + width: '12%', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts new file mode 100644 index 0000000000000..906e752d79aa0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const COLUMN_ACTIONS = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsLabel', + { + defaultMessage: 'Actions', + } +); + +export const ACTIONS_VIEW_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsViewLabel', + { + defaultMessage: 'View', + } +); + +export const ACTIONS_EDIT_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsEditLabel', + { + defaultMessage: 'Edit', + } +); + +export const ACTIONS_INSTALL_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsInstallLabel', + { + defaultMessage: 'Install', + } +); + +export const ACTIONS_RESTART_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsRestartLabel', + { + defaultMessage: 'Restart', + } +); + +export const COLUMN_NAME = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.nameLabel', + { + defaultMessage: 'Name', + } +); + +export const COLUMN_STATUS = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.statusLabel', + { + defaultMessage: 'Status', + } +); + +export const COLUMN_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.riskScoreLabel', + { + defaultMessage: 'Risk score', + } +); + +export const COLUMN_SEVERITY = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.severityLabel', + { + defaultMessage: 'Severity', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx index 4aaff21281d64..b6dce09c311e1 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx @@ -25,6 +25,10 @@ import { } from '@elastic/eui'; import type { EuiTabbedContentTab, EuiTabbedContentProps, EuiFlyoutProps } from '@elastic/eui'; +import { + DEFAULT_TRANSLATION_RISK_SCORE, + DEFAULT_TRANSLATION_SEVERITY, +} from '../../../../../common/siem_migrations/constants'; import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import { RuleOverviewTab, @@ -41,10 +45,6 @@ import { LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS, } from './constants'; import { TranslationTab } from './translation_tab'; -import { - DEFAULT_TRANSLATION_RISK_SCORE, - DEFAULT_TRANSLATION_SEVERITY, -} from '../../utils/constants'; const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflow { diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx index 3b13b9e631ccb..b7e06b4ea938a 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx @@ -5,103 +5,38 @@ * 2.0. */ -import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiText, EuiLink } from '@elastic/eui'; -import React, { useMemo } from 'react'; -import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import { useMemo } from 'react'; import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; -import { SeverityBadge } from '../../../common/components/severity_badge'; -import * as rulesI18n from '../../../detections/pages/detection_engine/rules/translations'; -import * as i18n from './translations'; -import { getNormalizedSeverity } from '../../../detection_engine/rule_management_ui/components/rules_table/helpers'; -import { StatusBadge } from '../components/status_badge'; -import { DEFAULT_TRANSLATION_RISK_SCORE, DEFAULT_TRANSLATION_SEVERITY } from '../utils/constants'; - -export type TableColumn = EuiBasicTableColumn; - -interface RuleNameProps { - name: string; - rule: RuleMigration; - openRulePreview: (rule: RuleMigration) => void; -} - -const RuleName = ({ name, rule, openRulePreview }: RuleNameProps) => { - return ( - { - openRulePreview(rule); - }} - data-test-subj="ruleName" - > - {name} - - ); -}; - -const createRuleNameColumn = ({ - openRulePreview, -}: { - openRulePreview: (rule: RuleMigration) => void; -}): TableColumn => { - return { - field: 'original_rule.title', - name: rulesI18n.COLUMN_RULE, - render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => ( - - ), - sortable: true, - truncateText: true, - width: '40%', - align: 'left', - }; -}; - -const STATUS_COLUMN: TableColumn = { - field: 'translation_result', - name: i18n.COLUMN_STATUS, - render: (value: RuleMigration['translation_result'], rule: RuleMigration) => ( - - ), - sortable: false, - truncateText: true, - width: '12%', -}; +import type { TableColumn } from '../components/rules_table_columns'; +import { + createActionsColumn, + createNameColumn, + createRiskScoreColumn, + createSeverityColumn, + createStatusColumn, +} from '../components/rules_table_columns'; export const useRulesTableColumns = ({ - openRulePreview, + disableActions, + openMigrationRulePreview, + installMigrationRule, }: { - openRulePreview: (rule: RuleMigration) => void; + disableActions?: boolean; + openMigrationRulePreview: (rule: RuleMigration) => void; + installMigrationRule: (migrationRule: RuleMigration, enable?: boolean) => void; }): TableColumn[] => { return useMemo( () => [ - createRuleNameColumn({ openRulePreview }), - STATUS_COLUMN, - { - field: 'risk_score', - name: rulesI18n.COLUMN_RISK_SCORE, - render: () => ( - - {DEFAULT_TRANSLATION_RISK_SCORE} - - ), - sortable: true, - truncateText: true, - width: '75px', - }, - { - field: 'elastic_rule.severity', - name: rulesI18n.COLUMN_SEVERITY, - render: (value?: Severity) => ( - - ), - sortable: ({ elastic_rule: elasticRule }: RuleMigration) => - getNormalizedSeverity( - (elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY - ), - truncateText: true, - width: '12%', - }, + createNameColumn({ openMigrationRulePreview }), + createStatusColumn(), + createRiskScoreColumn(), + createSeverityColumn(), + createActionsColumn({ + disableActions, + openMigrationRulePreview, + installMigrationRule, + }), ], - [openRulePreview] + [disableActions, installMigrationRule, openMigrationRulePreview] ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/translations.ts new file mode 100644 index 0000000000000..23f5a6e3849a0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const GET_MIGRATION_RULES_FAILURE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.getMigrationRulesFailDescription', + { + defaultMessage: 'Failed to fetch migration rules', + } +); + +export const INSTALL_MIGRATION_RULES_FAILURE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.installMigrationRulesFailDescription', + { + defaultMessage: 'Failed to install migration rules', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts new file mode 100644 index 0000000000000..27637daf142ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useGetMigrationRulesQuery } from '../api/hooks/use_get_migration_rules_query'; +import * as i18n from './translations'; + +export const useGetMigrationRules = (migrationId: string) => { + const { addError } = useAppToasts(); + + return useGetMigrationRulesQuery(migrationId, { + onError: (error) => { + addError(error, { title: i18n.GET_MIGRATION_RULES_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_all_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_all_migration_rules.ts new file mode 100644 index 0000000000000..105ea651d0a8c --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_all_migration_rules.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useInstallAllMigrationRulesMutation } from '../api/hooks/use_install_all_migration_rules_mutation'; +import * as i18n from './translations'; + +export const useInstallAllMigrationRules = (migrationId: string) => { + const { addError } = useAppToasts(); + + return useInstallAllMigrationRulesMutation(migrationId, { + onError: (error) => { + addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts new file mode 100644 index 0000000000000..dcc19f290f87f --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useInstallMigrationRulesMutation } from '../api/hooks/use_install_migration_rules_mutation'; +import * as i18n from './translations'; + +export const useInstallMigrationRules = (migrationId: string) => { + const { addError } = useAppToasts(); + + return useInstallMigrationRulesMutation(migrationId, { + onError: (error) => { + addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx index 26199616b3777..dabdb83cccbab 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx @@ -5,12 +5,11 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { EuiSkeletonLoading, EuiSkeletonText, EuiSkeletonTitle } from '@elastic/eui'; import type { RouteComponentProps } from 'react-router-dom'; import { useNavigation } from '../../../common/lib/kibana'; -import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; import { HeaderPage } from '../../../common/components/header_page'; import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { SecurityPageName } from '../../../app/types'; @@ -20,7 +19,6 @@ import { RulesTable } from '../components/rules_table'; import { NeedAdminForUpdateRulesCallOut } from '../../../detections/components/callouts/need_admin_for_update_callout'; import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; import { HeaderButtons } from '../components/header_buttons'; -import { useRulePreviewFlyout } from '../hooks/use_rule_preview_flyout'; import { UnknownMigration } from '../components/unknown_migration'; import { useLatestStats } from '../hooks/use_latest_stats'; @@ -66,24 +64,12 @@ export const RulesPage: React.FC = React.memo( navigateTo({ deepLinkId: SecurityPageName.siemMigrationsRules, path: selectedId }); }; - const ruleActionsFactory = useCallback( - (ruleMigration: RuleMigration, closeRulePreview: () => void) => { - // TODO: Add flyout action buttons - return null; - }, - [] - ); - - const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ - ruleActionsFactory, - }); - const content = useMemo(() => { if (!migrationId || !migrationsIds.includes(migrationId)) { return ; } - return ; - }, [migrationId, migrationsIds, openRulePreview]); + return ; + }, [migrationId, migrationsIds]); return ( <> @@ -108,7 +94,6 @@ export const RulesPage: React.FC = React.memo( } loadedContent={content} /> - {rulePreviewFlyout} ); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts index 4c704e97179c0..b395fa0199de8 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts @@ -11,3 +11,14 @@ export interface RuleMigrationTask extends RuleMigrationTaskStats { /** The sequential number of the migration */ number: number; } + +export interface InstallRulesProps { + migrationId: string; + ids: string[]; + signal?: AbortSignal; +} + +export interface InstallTranslatedRulesProps { + migrationId: string; + signal?: AbortSignal; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts index e6edb05b3a68a..0d880484877f6 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts @@ -38,7 +38,7 @@ export const registerSiemRuleMigrationsGetRoute = ( const ctx = await context.resolve(['securitySolution']); const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); - const migrationRules = await ruleMigrationsClient.data.rules.get(migrationId); + const migrationRules = await ruleMigrationsClient.data.rules.get({ migrationId }); return res.ok({ body: migrationRules }); } catch (err) { diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts index c6ea6b8bf897b..601b156aee040 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts @@ -17,6 +17,8 @@ import { registerSiemRuleMigrationsStatsAllRoute } from './stats_all'; import { registerSiemRuleMigrationsResourceUpsertRoute } from './resources/upsert'; import { registerSiemRuleMigrationsResourceGetRoute } from './resources/get'; import { registerSiemRuleMigrationsRetryRoute } from './retry'; +import { registerSiemRuleMigrationsInstallRoute } from './rules/install'; +import { registerSiemRuleMigrationsInstallTranslatedRoute } from './rules/install_translated'; export const registerSiemRuleMigrationsRoutes = ( router: SecuritySolutionPluginRouter, @@ -30,6 +32,8 @@ export const registerSiemRuleMigrationsRoutes = ( registerSiemRuleMigrationsRetryRoute(router, logger); registerSiemRuleMigrationsStatsRoute(router, logger); registerSiemRuleMigrationsStopRoute(router, logger); + registerSiemRuleMigrationsInstallRoute(router, logger); + registerSiemRuleMigrationsInstallTranslatedRoute(router, logger); registerSiemRuleMigrationsResourceUpsertRoute(router, logger); registerSiemRuleMigrationsResourceGetRoute(router, logger); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/rules/install.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/rules/install.ts new file mode 100644 index 0000000000000..659534891b289 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/rules/install.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../../../common/siem_migrations/constants'; +import type { InstallMigrationRulesResponse } from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { + InstallMigrationRulesRequestBody, + InstallMigrationRulesRequestParams, +} from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { withLicense } from '../util/with_license'; +import { installTranslated } from '../util/installation'; + +export const registerSiemRuleMigrationsInstallRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .post({ + path: SIEM_RULE_MIGRATION_INSTALL_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: buildRouteValidationWithZod(InstallMigrationRulesRequestParams), + body: buildRouteValidationWithZod(InstallMigrationRulesRequestBody), + }, + }, + }, + withLicense( + async (context, req, res): Promise> => { + const { migration_id: migrationId } = req.params; + const ids = req.body; + + try { + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); + + const securitySolutionContext = ctx.securitySolution; + const savedObjectsClient = ctx.core.savedObjects.client; + const rulesClient = ctx.alerting.getRulesClient(); + + await installTranslated({ + migrationId, + ids, + securitySolutionContext, + savedObjectsClient, + rulesClient, + logger, + }); + + return res.ok({ body: { installed: true } }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/rules/install_translated.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/rules/install_translated.ts new file mode 100644 index 0000000000000..ae4328e0ccf37 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/rules/install_translated.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../../../common/siem_migrations/constants'; +import type { InstallTranslatedMigrationRulesResponse } from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { InstallTranslatedMigrationRulesRequestParams } from '../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { withLicense } from '../util/with_license'; +import { installTranslated } from '../util/installation'; + +export const registerSiemRuleMigrationsInstallTranslatedRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .post({ + path: SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: buildRouteValidationWithZod(InstallTranslatedMigrationRulesRequestParams), + }, + }, + }, + withLicense( + async ( + context, + req, + res + ): Promise> => { + const { migration_id: migrationId } = req.params; + + try { + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); + + const securitySolutionContext = ctx.securitySolution; + const savedObjectsClient = ctx.core.savedObjects.client; + const rulesClient = ctx.alerting.getRulesClient(); + + await installTranslated({ + migrationId, + securitySolutionContext, + savedObjectsClient, + rulesClient, + logger, + }); + + return res.ok({ body: { installed: true } }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts new file mode 100644 index 0000000000000..ee211e8a935de --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; +import { + DEFAULT_TRANSLATION_RISK_SCORE, + DEFAULT_TRANSLATION_SEVERITY, +} from '../../../../../../common/siem_migrations/constants'; +import type { SecuritySolutionApiRequestHandlerContext } from '../../../../..'; +import { createPrebuiltRuleObjectsClient } from '../../../../detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client'; +import { performTimelinesInstallation } from '../../../../detection_engine/prebuilt_rules/logic/perform_timelines_installation'; +import { createPrebuiltRules } from '../../../../detection_engine/prebuilt_rules/logic/rule_objects/create_prebuilt_rules'; +import type { PrebuiltRuleAsset } from '../../../../detection_engine/prebuilt_rules'; +import { getRuleGroups } from '../../../../detection_engine/prebuilt_rules/model/rule_groups/get_rule_groups'; +import { fetchRuleVersionsTriad } from '../../../../detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad'; +import { createPrebuiltRuleAssetsClient } from '../../../../detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; +import type { IDetectionRulesClient } from '../../../../detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface'; +import type { RuleCreateProps } from '../../../../../../common/api/detection_engine'; +import type { UpdateRuleMigrationInput } from '../../data/rule_migrations_data_rules_client'; +import type { StoredRuleMigration } from '../../types'; + +const installPrebuiltRules = async ( + rulesToInstall: StoredRuleMigration[], + securitySolutionContext: SecuritySolutionApiRequestHandlerContext, + rulesClient: RulesClient, + savedObjectsClient: SavedObjectsClientContract, + detectionRulesClient: IDetectionRulesClient +): Promise => { + const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient); + const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + const ruleVersionsMap = await fetchRuleVersionsTriad({ + ruleAssetsClient, + ruleObjectsClient, + }); + const { currentRules, installableRules } = getRuleGroups(ruleVersionsMap); + + const rulesToUpdate: UpdateRuleMigrationInput[] = []; + const assetsToInstall: PrebuiltRuleAsset[] = []; + rulesToInstall.forEach((ruleToInstall) => { + // If prebuilt rule has already been install, then just update migration rule with the installed rule id + const installedRule = currentRules.find( + (rule) => rule.rule_id === ruleToInstall.elastic_rule?.prebuilt_rule_id + ); + if (installedRule) { + rulesToUpdate.push({ + id: ruleToInstall.id, + elastic_rule: { + id: installedRule.id, + }, + }); + return; + } + + // If prebuilt rule is not installed, then keep reference to install it + const installableRule = installableRules.find( + (rule) => rule.rule_id === ruleToInstall.elastic_rule?.prebuilt_rule_id + ); + if (installableRule) { + assetsToInstall.push(installableRule); + } + }); + + // Filter out any duplicates which can occur when multiple translated rules matched the same prebuilt rule + const filteredAssetsToInstall = assetsToInstall.filter( + (value, index, self) => index === self.findIndex((rule) => rule.rule_id === value.rule_id) + ); + + // TODO: we need to do an error handling which can happen during the rule installation + const { results: installedRules } = await createPrebuiltRules( + detectionRulesClient, + filteredAssetsToInstall + ); + await performTimelinesInstallation(securitySolutionContext); + + installedRules.forEach((installedRule) => { + const rules = rulesToInstall.filter( + (rule) => rule.elastic_rule?.prebuilt_rule_id === installedRule.result.rule_id + ); + rules.forEach((prebuiltRule) => { + rulesToUpdate.push({ + id: prebuiltRule.id, + elastic_rule: { + id: installedRule.result.id, + }, + }); + }); + }); + + return rulesToUpdate; +}; + +const installCustomRules = async ( + rulesToInstall: StoredRuleMigration[], + detectionRulesClient: IDetectionRulesClient, + logger: Logger +): Promise => { + const rulesToUpdate: UpdateRuleMigrationInput[] = []; + await Promise.all( + rulesToInstall.map(async (rule) => { + if (!rule.elastic_rule?.query || !rule.elastic_rule?.description) { + return; + } + try { + const payloadRule: RuleCreateProps = { + type: 'esql', + language: 'esql', + query: rule.elastic_rule.query, + name: rule.elastic_rule.title, + description: rule.elastic_rule.description, + severity: DEFAULT_TRANSLATION_SEVERITY, + risk_score: DEFAULT_TRANSLATION_RISK_SCORE, + }; + const createdRule = await detectionRulesClient.createCustomRule({ + params: payloadRule, + }); + rulesToUpdate.push({ + id: rule.id, + elastic_rule: { + id: createdRule.id, + }, + }); + } catch (err) { + // TODO: we need to do an error handling which can happen during the rule creation + logger.debug(`Could not create a rule because of error: ${JSON.stringify(err)}`); + } + }) + ); + return rulesToUpdate; +}; + +interface InstallTranslatedProps { + /** + * The migration id + */ + migrationId: string; + + /** + * If specified, then installable translated rules in theThe list will be installed, + * otherwise all installable translated rules will be installed. + */ + ids?: string[]; + + /** + * The security solution context + */ + securitySolutionContext: SecuritySolutionApiRequestHandlerContext; + + /** + * The rules client to create rules + */ + rulesClient: RulesClient; + + /** + * The saved objects client + */ + savedObjectsClient: SavedObjectsClientContract; + + /** + * The logger + */ + logger: Logger; +} + +export const installTranslated = async ({ + migrationId, + ids, + securitySolutionContext, + rulesClient, + savedObjectsClient, + logger, +}: InstallTranslatedProps) => { + const detectionRulesClient = securitySolutionContext.getDetectionRulesClient(); + const ruleMigrationsClient = securitySolutionContext.getSiemRuleMigrationsClient(); + + const rulesToInstall = await ruleMigrationsClient.data.rules.get({ + migrationId, + ids, + installable: true, + }); + + const { customRulesToInstall, prebuiltRulesToInstall } = rulesToInstall.reduce( + (acc, item) => { + if (item.elastic_rule?.prebuilt_rule_id) { + acc.prebuiltRulesToInstall.push(item); + } else { + acc.customRulesToInstall.push(item); + } + return acc; + }, + { customRulesToInstall: [], prebuiltRulesToInstall: [] } as { + customRulesToInstall: StoredRuleMigration[]; + prebuiltRulesToInstall: StoredRuleMigration[]; + } + ); + + const updatedPrebuiltRules = await installPrebuiltRules( + prebuiltRulesToInstall, + securitySolutionContext, + rulesClient, + savedObjectsClient, + detectionRulesClient + ); + + const updatedCustomRules = await installCustomRules( + customRulesToInstall, + detectionRulesClient, + logger + ); + + const rulesToUpdate: UpdateRuleMigrationInput[] = [ + ...updatedPrebuiltRules, + ...updatedCustomRules, + ]; + + if (rulesToUpdate.length) { + await ruleMigrationsClient.data.rules.update(rulesToUpdate); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts index 06e257b9862c5..0a82e2c311906 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts @@ -34,6 +34,13 @@ export type UpdateRuleMigrationInput = { elastic_rule?: Partial } & export type RuleMigrationDataStats = Omit; export type RuleMigrationAllDataStats = RuleMigrationDataStats[]; +export interface RuleMigrationFilterOptions { + migrationId: string; + status?: SiemMigrationStatus | SiemMigrationStatus[]; + ids?: string[]; + installable?: boolean; +} + /* BULK_MAX_SIZE defines the number to break down the bulk operations by. * The 500 number was chosen as a reasonable number to avoid large payloads. It can be adjusted if needed. */ @@ -101,9 +108,9 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient } /** Retrieves an array of rule documents of a specific migrations */ - async get(migrationId: string): Promise { + async get(filters: RuleMigrationFilterOptions): Promise { const index = await this.getIndexName(); - const query = this.getFilterQuery(migrationId); + const query = this.getFilterQuery(filters); const storedRuleMigrations = await this.esClient .search({ index, query, sort: '_doc' }) @@ -123,7 +130,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient */ async takePending(migrationId: string, size: number): Promise { const index = await this.getIndexName(); - const query = this.getFilterQuery(migrationId, SiemMigrationStatus.PENDING); + const query = this.getFilterQuery({ migrationId, status: SiemMigrationStatus.PENDING }); const storedRuleMigrations = await this.esClient .search({ index, query, sort: '_doc', size }) @@ -202,7 +209,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient { refresh = false }: { refresh?: boolean } = {} ): Promise { const index = await this.getIndexName(); - const query = this.getFilterQuery(migrationId, statusToQuery); + const query = this.getFilterQuery({ migrationId, status: statusToQuery }); const script = { source: `ctx._source['status'] = '${statusToUpdate}'` }; await this.esClient.updateByQuery({ index, query, script, refresh }).catch((error) => { this.logger.error(`Error updating rule migrations status: ${error.message}`); @@ -213,7 +220,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient /** Retrieves the stats for the rule migrations with the provided id */ async getStats(migrationId: string): Promise { const index = await this.getIndexName(); - const query = this.getFilterQuery(migrationId); + const query = this.getFilterQuery({ migrationId }); const aggregations = { pending: { filter: { term: { status: SiemMigrationStatus.PENDING } } }, processing: { filter: { term: { status: SiemMigrationStatus.PROCESSING } } }, @@ -283,10 +290,12 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient })); } - private getFilterQuery( - migrationId: string, - status?: SiemMigrationStatus | SiemMigrationStatus[] - ): QueryDslQueryContainer { + private getFilterQuery({ + migrationId, + status, + ids, + installable, + }: RuleMigrationFilterOptions): QueryDslQueryContainer { const filter: QueryDslQueryContainer[] = [{ term: { migration_id: migrationId } }]; if (status) { if (Array.isArray(status)) { @@ -295,6 +304,20 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient filter.push({ term: { status } }); } } + if (ids) { + filter.push({ terms: { _id: ids } }); + } + if (installable) { + filter.push( + { term: { translation_result: 'full' } }, + { + nested: { + path: 'elastic_rule', + query: { bool: { must_not: { exists: { field: 'elastic_rule.id' } } } }, + }, + } + ); + } return { bool: { filter } }; } } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts index 056b41bf088a0..8f0460165f290 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts @@ -6,6 +6,7 @@ */ import { JsonOutputParser } from '@langchain/core/output_parsers'; +import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; import type { ChatModel } from '../../../util/actions_client_chat'; import { filterPrebuiltRules, type PrebuiltRulesMapByName } from '../../../util/prebuilt_rules'; import type { GraphNode } from '../../types'; @@ -51,6 +52,7 @@ export const getMatchPrebuiltRuleNode = prebuilt_rule_id: result.rule.rule_id, id: result.installedRuleId, }, + translation_result: SiemMigrationRuleTranslationResult.FULL, }; } } diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index ed598c941ed56..a0b1bf74926b4 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -107,7 +107,12 @@ import { InitEntityEngineRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/init.gen'; import { InitEntityStoreRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/enable.gen'; +import { + InstallMigrationRulesRequestParamsInput, + InstallMigrationRulesRequestBodyInput, +} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen'; import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen'; +import { InstallTranslatedMigrationRulesRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen'; import { ListEntitiesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/entities/list_entities.gen'; import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen'; import { PatchTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/patch_timelines/patch_timeline_route.gen'; @@ -1046,6 +1051,22 @@ finalize it. .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Installs migration rules + */ + installMigrationRules(props: InstallMigrationRulesProps, kibanaSpace: string = 'default') { + return supertest + .post( + routeWithNamespace( + replaceParams('/internal/siem_migrations/rules/{migration_id}/install', props.params), + kibanaSpace + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Install and update all Elastic prebuilt detection rules and Timelines. */ @@ -1070,6 +1091,27 @@ finalize it. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Installs all translated migration rules + */ + installTranslatedMigrationRules( + props: InstallTranslatedMigrationRulesProps, + kibanaSpace: string = 'default' + ) { + return supertest + .post( + routeWithNamespace( + replaceParams( + '/internal/siem_migrations/rules/{migration_id}/install_translated', + props.params + ), + kibanaSpace + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, internalUploadAssetCriticalityRecords(kibanaSpace: string = 'default') { return supertest .post(routeWithNamespace('/internal/asset_criticality/upload_csv', kibanaSpace)) @@ -1653,9 +1695,16 @@ export interface InitEntityEngineProps { export interface InitEntityStoreProps { body: InitEntityStoreRequestBodyInput; } +export interface InstallMigrationRulesProps { + params: InstallMigrationRulesRequestParamsInput; + body: InstallMigrationRulesRequestBodyInput; +} export interface InstallPrepackedTimelinesProps { body: InstallPrepackedTimelinesRequestBodyInput; } +export interface InstallTranslatedMigrationRulesProps { + params: InstallTranslatedMigrationRulesRequestParamsInput; +} export interface ListEntitiesProps { query: ListEntitiesRequestQueryInput; }