mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Rules migration] Improvements & fixes (#206658)
## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details This PR includes next improvements and fixes ### Improvements 1. [PR feedback] Improved filtering: https://github.com/elastic/kibana/pull/206089#discussion_r1913256593 2. [PR feedback] Use variable instead of massive destructing object: https://github.com/elastic/kibana/pull/206089#discussion_r1913268303 3. `Upload` missing resources button 4. Show comment as a tooltip within the `Status` column for the failed rule  ### Fixes 1. Better error handling 2. Fetch all existing rules (via batches search) instead of 10k limit > [!NOTE] > This feature needs `siemMigrationsEnabled` experimental flag enabled to work. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5ab8a52187
commit
bd19bcc005
32 changed files with 264 additions and 509 deletions
|
@ -386,8 +386,6 @@ import type {
|
|||
InstallMigrationRulesRequestParamsInput,
|
||||
InstallMigrationRulesRequestBodyInput,
|
||||
InstallMigrationRulesResponse,
|
||||
InstallTranslatedMigrationRulesRequestParamsInput,
|
||||
InstallTranslatedMigrationRulesResponse,
|
||||
StartRuleMigrationRequestParamsInput,
|
||||
StartRuleMigrationRequestBodyInput,
|
||||
StartRuleMigrationResponse,
|
||||
|
@ -1736,24 +1734,6 @@ 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<InstallTranslatedMigrationRulesResponse>({
|
||||
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
|
||||
|
@ -2541,9 +2521,6 @@ export interface InstallMigrationRulesProps {
|
|||
export interface InstallPrepackedTimelinesProps {
|
||||
body: InstallPrepackedTimelinesRequestBodyInput;
|
||||
}
|
||||
export interface InstallTranslatedMigrationRulesProps {
|
||||
params: InstallTranslatedMigrationRulesRequestParamsInput;
|
||||
}
|
||||
export interface InternalUploadAssetCriticalityRecordsProps {
|
||||
attachment: FormData;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ export const SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH =
|
|||
`${SIEM_RULE_MIGRATION_PATH}/translation_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_MIGRATIONS_PREBUILT_RULES_PATH =
|
||||
`${SIEM_RULE_MIGRATION_PATH}/prebuilt_rules` as const;
|
||||
|
||||
|
@ -64,16 +62,3 @@ export const DEFAULT_TRANSLATION_FIELDS = {
|
|||
to: 'now',
|
||||
interval: '5m',
|
||||
} as const;
|
||||
|
||||
export enum AuthorFilter {
|
||||
ELASTIC = 'elastic',
|
||||
CUSTOM = 'custom',
|
||||
}
|
||||
|
||||
export enum StatusFilter {
|
||||
INSTALLED = 'installed',
|
||||
TRANSLATED = 'translated',
|
||||
PARTIALLY_TRANSLATED = 'partially_translated',
|
||||
UNTRANSLATABLE = 'untranslatable',
|
||||
FAILED = 'failed',
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ export type InstallMigrationRulesRequestParamsInput = z.input<
|
|||
|
||||
export type InstallMigrationRulesRequestBody = z.infer<typeof InstallMigrationRulesRequestBody>;
|
||||
export const InstallMigrationRulesRequestBody = z.object({
|
||||
ids: z.array(NonEmptyString),
|
||||
ids: z.array(NonEmptyString).optional(),
|
||||
/**
|
||||
* Indicates whether installed rules should be enabled
|
||||
*/
|
||||
|
@ -206,29 +206,9 @@ export type InstallMigrationRulesRequestBodyInput = z.input<
|
|||
export type InstallMigrationRulesResponse = z.infer<typeof InstallMigrationRulesResponse>;
|
||||
export const InstallMigrationRulesResponse = z.object({
|
||||
/**
|
||||
* Indicates rules migrations have been installed.
|
||||
* Indicates the number of successfully installed migration rules.
|
||||
*/
|
||||
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(),
|
||||
installed: z.number(),
|
||||
});
|
||||
|
||||
export type StartRuleMigrationRequestParams = z.infer<typeof StartRuleMigrationRequestParams>;
|
||||
|
|
|
@ -237,8 +237,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- ids
|
||||
properties:
|
||||
ids:
|
||||
type: array
|
||||
|
@ -259,37 +257,8 @@ paths:
|
|||
- 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/api/model/primitives.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.
|
||||
type: number
|
||||
description: Indicates the number of successfully installed migration rules.
|
||||
|
||||
/internal/siem_migrations/rules/{migration_id}/start:
|
||||
put:
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { SiemMigrationStatus } from './constants';
|
||||
|
||||
export interface RuleMigrationFilters {
|
||||
status?: SiemMigrationStatus | SiemMigrationStatus[];
|
||||
ids?: string[];
|
||||
installed?: boolean;
|
||||
installable?: boolean;
|
||||
prebuilt?: boolean;
|
||||
failed?: boolean;
|
||||
fullyTranslated?: boolean;
|
||||
partiallyTranslated?: boolean;
|
||||
untranslatable?: boolean;
|
||||
searchTerm?: string;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||
|
||||
import type { RuleMigrationFilters } from '../../../../common/siem_migrations/types';
|
||||
import type { UpdateRuleMigrationData } from '../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import type { LangSmithOptions } from '../../../../common/siem_migrations/model/common.gen';
|
||||
import { KibanaServices } from '../../../common/lib/kibana';
|
||||
|
@ -15,7 +16,6 @@ import type { SiemMigrationRetryFilter } from '../../../../common/siem_migration
|
|||
import {
|
||||
SIEM_RULE_MIGRATIONS_PATH,
|
||||
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,
|
||||
|
@ -32,7 +32,6 @@ import type {
|
|||
GetAllStatsRuleMigrationResponse,
|
||||
GetRuleMigrationResponse,
|
||||
GetRuleMigrationTranslationStatsResponse,
|
||||
InstallTranslatedMigrationRulesResponse,
|
||||
InstallMigrationRulesResponse,
|
||||
StartRuleMigrationRequestBody,
|
||||
GetRuleMigrationStatsResponse,
|
||||
|
@ -176,22 +175,8 @@ export interface GetRuleMigrationParams {
|
|||
sortField?: string;
|
||||
/** Optional direction to sort results by */
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
/** Optional search term to filter documents */
|
||||
searchTerm?: string;
|
||||
/** Optional rules ids to filter documents */
|
||||
ids?: string[];
|
||||
/** Optional attribute to retrieve prebuilt migration rules */
|
||||
isPrebuilt?: boolean;
|
||||
/** Optional attribute to retrieve installed migration rules */
|
||||
isInstalled?: boolean;
|
||||
/** Optional attribute to retrieve fully translated migration rules */
|
||||
isFullyTranslated?: boolean;
|
||||
/** Optional attribute to retrieve partially translated migration rules */
|
||||
isPartiallyTranslated?: boolean;
|
||||
/** Optional attribute to retrieve untranslated migration rules */
|
||||
isUntranslatable?: boolean;
|
||||
/** Optional attribute to retrieve failed migration rules */
|
||||
isFailed?: boolean;
|
||||
/** Optional parameter to filter documents */
|
||||
filters?: RuleMigrationFilters;
|
||||
/** Optional AbortSignal for cancelling request */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
@ -202,14 +187,7 @@ export const getRuleMigrations = async ({
|
|||
perPage,
|
||||
sortField,
|
||||
sortDirection,
|
||||
searchTerm,
|
||||
ids,
|
||||
isPrebuilt,
|
||||
isInstalled,
|
||||
isFullyTranslated,
|
||||
isPartiallyTranslated,
|
||||
isUntranslatable,
|
||||
isFailed,
|
||||
filters,
|
||||
signal,
|
||||
}: GetRuleMigrationParams): Promise<GetRuleMigrationResponse> => {
|
||||
return KibanaServices.get().http.get<GetRuleMigrationResponse>(
|
||||
|
@ -221,14 +199,14 @@ export const getRuleMigrations = async ({
|
|||
per_page: perPage,
|
||||
sort_field: sortField,
|
||||
sort_direction: sortDirection,
|
||||
search_term: searchTerm,
|
||||
ids,
|
||||
is_prebuilt: isPrebuilt,
|
||||
is_installed: isInstalled,
|
||||
is_fully_translated: isFullyTranslated,
|
||||
is_partially_translated: isPartiallyTranslated,
|
||||
is_untranslatable: isUntranslatable,
|
||||
is_failed: isFailed,
|
||||
search_term: filters?.searchTerm,
|
||||
ids: filters?.ids,
|
||||
is_prebuilt: filters?.prebuilt,
|
||||
is_installed: filters?.installed,
|
||||
is_fully_translated: filters?.fullyTranslated,
|
||||
is_partially_translated: filters?.partiallyTranslated,
|
||||
is_untranslatable: filters?.untranslatable,
|
||||
is_failed: filters?.failed,
|
||||
},
|
||||
signal,
|
||||
}
|
||||
|
@ -258,7 +236,7 @@ export interface InstallRulesParams {
|
|||
/** `id` of the migration to install rules for */
|
||||
migrationId: string;
|
||||
/** The rule ids to install */
|
||||
ids: string[];
|
||||
ids?: string[];
|
||||
/** Optional indicator to enable the installed rule */
|
||||
enabled?: boolean;
|
||||
/** Optional AbortSignal for cancelling request */
|
||||
|
@ -277,23 +255,6 @@ export const installMigrationRules = async ({
|
|||
);
|
||||
};
|
||||
|
||||
export interface InstallTranslatedRulesParams {
|
||||
/** `id` of the migration to install rules for */
|
||||
migrationId: string;
|
||||
/** Optional AbortSignal for cancelling request */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
/** Installs all the translated rules for a specific migration. */
|
||||
export const installTranslatedMigrationRules = async ({
|
||||
migrationId,
|
||||
signal,
|
||||
}: InstallTranslatedRulesParams): Promise<InstallTranslatedMigrationRulesResponse> => {
|
||||
return KibanaServices.get().http.post<InstallTranslatedMigrationRulesResponse>(
|
||||
replaceParams(SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, { migration_id: migrationId }),
|
||||
{ version: '1', signal }
|
||||
);
|
||||
};
|
||||
|
||||
export interface GetRuleMigrationsPrebuiltRulesParams {
|
||||
/** `id` of the migration to install rules for */
|
||||
migrationId: string;
|
||||
|
|
|
@ -101,7 +101,7 @@ export const MigrationRuleDetailsFlyout: React.FC<MigrationRuleDetailsFlyoutProp
|
|||
|
||||
const handleTranslationUpdate = useCallback(
|
||||
async (ruleName: string, ruleQuery: string) => {
|
||||
if (!ruleMigration || isLoading) {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
setIsUpdating(true);
|
||||
|
@ -139,13 +139,11 @@ export const MigrationRuleDetailsFlyout: React.FC<MigrationRuleDetailsFlyoutProp
|
|||
name: i18n.TRANSLATION_TAB_LABEL,
|
||||
content: (
|
||||
<TabContentPadding>
|
||||
{ruleMigration && (
|
||||
<TranslationTab
|
||||
ruleMigration={ruleMigration}
|
||||
matchedPrebuiltRule={matchedPrebuiltRule}
|
||||
onTranslationUpdate={handleTranslationUpdate}
|
||||
/>
|
||||
)}
|
||||
<TranslationTab
|
||||
ruleMigration={ruleMigration}
|
||||
matchedPrebuiltRule={matchedPrebuiltRule}
|
||||
onTranslationUpdate={handleTranslationUpdate}
|
||||
/>
|
||||
</TabContentPadding>
|
||||
),
|
||||
}),
|
||||
|
@ -242,7 +240,7 @@ export const MigrationRuleDetailsFlyout: React.FC<MigrationRuleDetailsFlyoutProp
|
|||
<EuiTitle size="m">
|
||||
<h2 id={migrationsRulesFlyoutTitleId}>
|
||||
{ruleDetailsToOverview?.name ??
|
||||
ruleMigration?.original_rule.title ??
|
||||
ruleMigration.original_rule.title ??
|
||||
i18n.UNKNOWN_MIGRATION_RULE_TITLE}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
|
|
@ -10,10 +10,8 @@ import type { EuiCommentProps } from '@elastic/eui';
|
|||
import { EuiCommentList, EuiMarkdownFormat, EuiSpacer } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import { AssistantAvatar } from '@kbn/ai-assistant-icon';
|
||||
import {
|
||||
RuleMigrationStatusEnum,
|
||||
type RuleMigration,
|
||||
} from '../../../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import { type RuleMigration } from '../../../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import { RuleTranslationResult } from '../../../../../../../common/siem_migrations/constants';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface SummaryTabProps {
|
||||
|
@ -33,8 +31,8 @@ export const SummaryTab: React.FC<SummaryTabProps> = React.memo(({ ruleMigration
|
|||
timelineAvatarAriaLabel: i18n.ASSISTANT_USERNAME,
|
||||
timelineAvatar: <AssistantAvatar name="machine" size="l" color="subdued" />,
|
||||
event:
|
||||
ruleMigration.status === RuleMigrationStatusEnum.failed
|
||||
? i18n.COMMENT_EVENT_FAILED
|
||||
ruleMigration.translation_result === RuleTranslationResult.UNTRANSLATABLE
|
||||
? i18n.COMMENT_EVENT_UNTRANSLATABLE
|
||||
: i18n.COMMENT_EVENT_TRANSLATED,
|
||||
timestamp,
|
||||
children: <EuiMarkdownFormat textSize="s">{comment}</EuiMarkdownFormat>,
|
||||
|
|
|
@ -28,8 +28,8 @@ export const COMMENT_EVENT_TRANSLATED = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const COMMENT_EVENT_FAILED = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.translationDetails.summaryTab.commentEvent.failedLabel',
|
||||
export const COMMENT_EVENT_UNTRANSLATABLE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.translationDetails.summaryTab.commentEvent.untranslatableLabel',
|
||||
{
|
||||
defaultMessage: 'failed to translate',
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ export const CALLOUT_PARTIALLY_TRANSLATED_RULE_DESCRIPTION = i18n.translate(
|
|||
'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.partiallyTranslatedRuleCalloutDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'To save this rule, finish the query and define its severity. If you need help, please contact Elastic support.',
|
||||
'To save this rule, finish writing the query. If you need help, please contact Elastic support.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|||
import type { EuiSelectableOption } from '@elastic/eui';
|
||||
import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui';
|
||||
import type { EuiSelectableOnChangeEvent } from '@elastic/eui/src/components/selectable/selectable';
|
||||
import { AuthorFilter } from '../../../../../../common/siem_migrations/constants';
|
||||
import { AuthorFilter } from '../../../types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const AUTHOR_FILTER_POPOVER_WIDTH = 150;
|
||||
|
|
|
@ -7,18 +7,10 @@
|
|||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiFilterGroup } from '@elastic/eui';
|
||||
import type {
|
||||
AuthorFilter,
|
||||
StatusFilter,
|
||||
} from '../../../../../../common/siem_migrations/constants';
|
||||
import type { AuthorFilter, FilterOptions, StatusFilter } from '../../../types';
|
||||
import { StatusFilterButton } from './status';
|
||||
import { AuthorFilterButton } from './author';
|
||||
|
||||
export interface FilterOptions {
|
||||
status?: StatusFilter;
|
||||
author?: AuthorFilter;
|
||||
}
|
||||
|
||||
export interface MigrationRulesFilterProps {
|
||||
filterOptions?: FilterOptions;
|
||||
onFilterOptionsChanged: (filterOptions?: FilterOptions) => void;
|
||||
|
|
|
@ -9,12 +9,10 @@ import React, { useCallback, useState } from 'react';
|
|||
import type { EuiSelectableOption } from '@elastic/eui';
|
||||
import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui';
|
||||
import type { EuiSelectableOnChangeEvent } from '@elastic/eui/src/components/selectable/selectable';
|
||||
import {
|
||||
RuleTranslationResult,
|
||||
StatusFilter,
|
||||
} from '../../../../../../common/siem_migrations/constants';
|
||||
import * as i18n from './translations';
|
||||
import { RuleTranslationResult } from '../../../../../../common/siem_migrations/constants';
|
||||
import { convertTranslationResultIntoText } from '../../../utils/translation_results';
|
||||
import { StatusFilter } from '../../../types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const STATUS_FILTER_POPOVER_WIDTH = 250;
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* 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 { AuthorFilter, StatusFilter } from '../../../../../common/siem_migrations/constants';
|
||||
import type { FilterOptions } from './filters';
|
||||
|
||||
export const convertFilterOptions = (filterOptions?: FilterOptions) => {
|
||||
return {
|
||||
...(filterOptions?.author === AuthorFilter.ELASTIC ? { isPrebuilt: true } : {}),
|
||||
...(filterOptions?.author === AuthorFilter.CUSTOM ? { isPrebuilt: false } : {}),
|
||||
...(filterOptions?.status === StatusFilter.FAILED ? { isFailed: true } : {}),
|
||||
...(filterOptions?.status === StatusFilter.INSTALLED ? { isInstalled: true } : {}),
|
||||
...(filterOptions?.status === StatusFilter.TRANSLATED
|
||||
? { isInstalled: false, isFullyTranslated: true }
|
||||
: {}),
|
||||
...(filterOptions?.status === StatusFilter.PARTIALLY_TRANSLATED
|
||||
? { isPartiallyTranslated: true }
|
||||
: {}),
|
||||
...(filterOptions?.status === StatusFilter.UNTRANSLATABLE ? { isUntranslatable: true } : {}),
|
||||
};
|
||||
};
|
|
@ -27,7 +27,6 @@ import { useMigrationRulesTableColumns } from '../../hooks/use_migration_rules_t
|
|||
import { useMigrationRuleDetailsFlyout } from '../../hooks/use_migration_rule_preview_flyout';
|
||||
import { useInstallMigrationRules } from '../../logic/use_install_migration_rules';
|
||||
import { useGetMigrationRules } from '../../logic/use_get_migration_rules';
|
||||
import { useInstallTranslatedMigrationRules } from '../../logic/use_install_translated_migration_rules';
|
||||
import { useGetMigrationTranslationStats } from '../../logic/use_get_migration_translation_stats';
|
||||
import { useGetMigrationPrebuiltRules } from '../../logic/use_get_migration_prebuilt_rules';
|
||||
import * as logicI18n from '../../logic/translations';
|
||||
|
@ -39,9 +38,9 @@ import {
|
|||
} from '../../../../../common/siem_migrations/constants';
|
||||
import * as i18n from './translations';
|
||||
import { useStartMigration } from '../../service/hooks/use_start_migration';
|
||||
import type { FilterOptions } from './filters';
|
||||
import type { FilterOptions } from '../../types';
|
||||
import { MigrationRulesFilter } from './filters';
|
||||
import { convertFilterOptions } from './helpers';
|
||||
import { convertFilterOptions } from './utils/filters';
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT_FIELD = 'translation_result';
|
||||
|
@ -100,8 +99,10 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
|
|||
perPage: pageSize,
|
||||
sortField,
|
||||
sortDirection,
|
||||
searchTerm,
|
||||
...convertFilterOptions(filterOptions),
|
||||
filters: {
|
||||
searchTerm,
|
||||
...convertFilterOptions(filterOptions),
|
||||
},
|
||||
});
|
||||
|
||||
const [selectedRuleMigrations, setSelectedRuleMigrations] = useState<RuleMigration[]>([]);
|
||||
|
@ -158,13 +159,11 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
|
|||
}, []);
|
||||
|
||||
const { mutateAsync: installMigrationRules } = useInstallMigrationRules(migrationId);
|
||||
const { mutateAsync: installTranslatedMigrationRules } =
|
||||
useInstallTranslatedMigrationRules(migrationId);
|
||||
const { startMigration, isLoading: isRetryLoading } = useStartMigration(refetchData);
|
||||
|
||||
const [isTableLoading, setTableLoading] = useState(false);
|
||||
const installSingleRule = useCallback(
|
||||
async (migrationRule: RuleMigration, enabled = false) => {
|
||||
async (migrationRule: RuleMigration, enabled?: boolean) => {
|
||||
setTableLoading(true);
|
||||
try {
|
||||
await installMigrationRules({ ids: [migrationRule.id], enabled });
|
||||
|
@ -178,7 +177,7 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
|
|||
);
|
||||
|
||||
const installSelectedRule = useCallback(
|
||||
async (enabled = false) => {
|
||||
async (enabled?: boolean) => {
|
||||
setTableLoading(true);
|
||||
try {
|
||||
await installMigrationRules({
|
||||
|
@ -196,17 +195,17 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
|
|||
);
|
||||
|
||||
const installTranslatedRules = useCallback(
|
||||
async (enable?: boolean) => {
|
||||
async (enabled?: boolean) => {
|
||||
setTableLoading(true);
|
||||
try {
|
||||
await installTranslatedMigrationRules();
|
||||
await installMigrationRules({ enabled });
|
||||
} catch (error) {
|
||||
addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE });
|
||||
} finally {
|
||||
setTableLoading(false);
|
||||
}
|
||||
},
|
||||
[addError, installTranslatedMigrationRules]
|
||||
[addError, installMigrationRules]
|
||||
);
|
||||
|
||||
const reprocessFailedRules = useCallback(async () => {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { RuleMigrationFilters } from '../../../../../../common/siem_migrations/types';
|
||||
import type { FilterOptions } from '../../../types';
|
||||
import { AuthorFilter, StatusFilter } from '../../../types';
|
||||
|
||||
const AUTHOR_FILTERS: Record<AuthorFilter, RuleMigrationFilters> = {
|
||||
[AuthorFilter.ELASTIC]: { prebuilt: true },
|
||||
[AuthorFilter.CUSTOM]: { prebuilt: false },
|
||||
};
|
||||
|
||||
const STATUS_FILTERS: Record<StatusFilter, RuleMigrationFilters> = {
|
||||
[StatusFilter.FAILED]: { failed: true },
|
||||
[StatusFilter.INSTALLED]: { installed: true },
|
||||
[StatusFilter.TRANSLATED]: { installed: false, fullyTranslated: true },
|
||||
[StatusFilter.PARTIALLY_TRANSLATED]: { partiallyTranslated: true },
|
||||
[StatusFilter.UNTRANSLATABLE]: { untranslatable: true },
|
||||
};
|
||||
|
||||
export const convertFilterOptions = (filterOptions?: FilterOptions) => {
|
||||
const filters: RuleMigrationFilters = {};
|
||||
if (filterOptions?.author) {
|
||||
Object.assign(filters, AUTHOR_FILTERS[filterOptions.author]);
|
||||
}
|
||||
if (filterOptions?.status) {
|
||||
Object.assign(filters, STATUS_FILTERS[filterOptions.status]);
|
||||
}
|
||||
return filters;
|
||||
};
|
|
@ -49,8 +49,11 @@ export const StatusBadge: React.FC<StatusBadgeProps> = React.memo(
|
|||
|
||||
// Failed
|
||||
if (migrationRule.status === RuleMigrationStatusEnum.failed) {
|
||||
const tooltipMessage = migrationRule.comments?.length
|
||||
? migrationRule.comments[0]
|
||||
: i18n.RULE_STATUS_FAILED;
|
||||
return (
|
||||
<EuiToolTip content={i18n.RULE_STATUS_FAILED}>
|
||||
<EuiToolTip content={tooltipMessage}>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="warningFilled" color="danger" />
|
||||
|
|
|
@ -28,6 +28,12 @@ export const GET_MIGRATION_TRANSLATION_STATS_FAILURE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const INSTALL_MIGRATION_RULES_SUCCESS = (succeeded: number) =>
|
||||
i18n.translate('xpack.securitySolution.siemMigrations.rules.installMigrationRulesSuccess', {
|
||||
defaultMessage: '{succeeded, plural, one {# rule} other {# rules}} installed successfully.',
|
||||
values: { succeeded },
|
||||
});
|
||||
|
||||
export const INSTALL_MIGRATION_RULES_FAILURE = i18n.translate(
|
||||
'xpack.securitySolution.siemMigrations.rules.installMigrationRulesFailDescription',
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||
import { useCallback } from 'react';
|
||||
import type { RuleMigrationFilters } from '../../../../common/siem_migrations/types';
|
||||
import { SIEM_RULE_MIGRATION_PATH } from '../../../../common/siem_migrations/constants';
|
||||
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||
import * as i18n from './translations';
|
||||
|
@ -20,14 +21,7 @@ export const useGetMigrationRules = (params: {
|
|||
perPage?: number;
|
||||
sortField?: string;
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
searchTerm?: string;
|
||||
ids?: string[];
|
||||
isPrebuilt?: boolean;
|
||||
isInstalled?: boolean;
|
||||
isFullyTranslated?: boolean;
|
||||
isPartiallyTranslated?: boolean;
|
||||
isUntranslatable?: boolean;
|
||||
isFailed?: boolean;
|
||||
filters?: RuleMigrationFilters;
|
||||
}) => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
|
|
|
@ -17,15 +17,18 @@ import { installMigrationRules } from '../api';
|
|||
export const INSTALL_MIGRATION_RULES_MUTATION_KEY = ['POST', SIEM_RULE_MIGRATION_INSTALL_PATH];
|
||||
|
||||
export const useInstallMigrationRules = (migrationId: string) => {
|
||||
const { addError } = useAppToasts();
|
||||
const { addError, addSuccess } = useAppToasts();
|
||||
|
||||
const invalidateGetRuleMigrations = useInvalidateGetMigrationRules();
|
||||
const invalidateGetMigrationTranslationStats = useInvalidateGetMigrationTranslationStats();
|
||||
|
||||
return useMutation<InstallMigrationRulesResponse, Error, { ids: string[]; enabled: boolean }>(
|
||||
({ ids, enabled = false }) => installMigrationRules({ migrationId, ids, enabled }),
|
||||
return useMutation<InstallMigrationRulesResponse, Error, { ids?: string[]; enabled?: boolean }>(
|
||||
({ ids, enabled }) => installMigrationRules({ migrationId, ids, enabled }),
|
||||
{
|
||||
mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY,
|
||||
onSuccess: ({ installed }) => {
|
||||
addSuccess(i18n.INSTALL_MIGRATION_RULES_SUCCESS(installed));
|
||||
},
|
||||
onError: (error) => {
|
||||
addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE });
|
||||
},
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* 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 { 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 { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||
import * as i18n from './translations';
|
||||
import { useInvalidateGetMigrationRules } from './use_get_migration_rules';
|
||||
import { useInvalidateGetMigrationTranslationStats } from './use_get_migration_translation_stats';
|
||||
import { installTranslatedMigrationRules } from '../api';
|
||||
|
||||
export const INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY = [
|
||||
'POST',
|
||||
SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH,
|
||||
];
|
||||
|
||||
export const useInstallTranslatedMigrationRules = (migrationId: string) => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
const invalidateGetRuleMigrations = useInvalidateGetMigrationRules();
|
||||
const invalidateGetMigrationTranslationStats = useInvalidateGetMigrationTranslationStats();
|
||||
|
||||
return useMutation<InstallTranslatedMigrationRulesResponse, Error>(
|
||||
() => installTranslatedMigrationRules({ migrationId }),
|
||||
{
|
||||
mutationKey: INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY,
|
||||
onError: (error) => {
|
||||
addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE });
|
||||
},
|
||||
onSettled: () => {
|
||||
invalidateGetRuleMigrations(migrationId);
|
||||
invalidateGetMigrationTranslationStats(migrationId);
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { EuiSkeletonLoading, EuiSkeletonText, EuiSkeletonTitle } from '@elastic/eui';
|
||||
import { EuiSkeletonLoading, EuiSkeletonText, EuiSkeletonTitle, EuiSpacer } from '@elastic/eui';
|
||||
import type { RouteComponentProps } from 'react-router-dom';
|
||||
import type { RelatedIntegration } from '../../../../common/api/detection_engine';
|
||||
import { SiemMigrationTaskStatus } from '../../../../common/siem_migrations/constants';
|
||||
|
@ -29,6 +29,7 @@ import { useInvalidateGetMigrationRules } from '../logic/use_get_migration_rules
|
|||
import { useInvalidateGetMigrationTranslationStats } from '../logic/use_get_migration_translation_stats';
|
||||
import { useGetIntegrations } from '../service/hooks/use_get_integrations';
|
||||
import { PageTitle } from './page_title';
|
||||
import { RuleMigrationsUploadMissingPanel } from '../components/migration_status_panels/upload_missing_panel';
|
||||
|
||||
type MigrationRulesPageProps = RouteComponentProps<{ migrationId?: string }>;
|
||||
|
||||
|
@ -99,19 +100,24 @@ export const MigrationRulesPage: React.FC<MigrationRulesPageProps> = React.memo(
|
|||
if (!migrationId || !migrationStats) {
|
||||
return <UnknownMigration />;
|
||||
}
|
||||
if (migrationStats.status === SiemMigrationTaskStatus.FINISHED) {
|
||||
return (
|
||||
<MigrationRulesTable
|
||||
migrationId={migrationId}
|
||||
refetchData={refetchData}
|
||||
integrations={integrations}
|
||||
isIntegrationsLoading={isIntegrationsLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<RuleMigrationDataInputWrapper onFlyoutClosed={refetchData}>
|
||||
<>
|
||||
{migrationStats.status === SiemMigrationTaskStatus.FINISHED && (
|
||||
<>
|
||||
<RuleMigrationsUploadMissingPanel
|
||||
migrationStats={migrationStats}
|
||||
topSpacerSize="s"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<MigrationRulesTable
|
||||
migrationId={migrationId}
|
||||
refetchData={refetchData}
|
||||
integrations={integrations}
|
||||
isIntegrationsLoading={isIntegrationsLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{migrationStats.status === SiemMigrationTaskStatus.READY && (
|
||||
<MigrationReadyPanel migrationStats={migrationStats} />
|
||||
)}
|
||||
|
|
|
@ -13,3 +13,21 @@ export interface RuleMigrationStats extends RuleMigrationTaskStats {
|
|||
/** The sequential number of the migration */
|
||||
number: number;
|
||||
}
|
||||
|
||||
export enum AuthorFilter {
|
||||
ELASTIC = 'elastic',
|
||||
CUSTOM = 'custom',
|
||||
}
|
||||
|
||||
export enum StatusFilter {
|
||||
INSTALLED = 'installed',
|
||||
TRANSLATED = 'translated',
|
||||
PARTIALLY_TRANSLATED = 'partially_translated',
|
||||
UNTRANSLATABLE = 'untranslatable',
|
||||
FAILED = 'failed',
|
||||
}
|
||||
|
||||
export interface FilterOptions {
|
||||
status?: StatusFilter;
|
||||
author?: AuthorFilter;
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const MAX_CUSTOM_RULES_TO_CREATE_IN_PARALLEL = 50;
|
||||
export const MAX_PREBUILT_RULES_TO_FETCH = 10_000 as const;
|
||||
export const MAX_TRANSLATED_RULES_TO_INSTALL = 10_000 as const;
|
|
@ -18,7 +18,6 @@ import { registerSiemRuleMigrationsStatsAllRoute } from './stats_all';
|
|||
import { registerSiemRuleMigrationsResourceUpsertRoute } from './resources/upsert';
|
||||
import { registerSiemRuleMigrationsResourceGetRoute } from './resources/get';
|
||||
import { registerSiemRuleMigrationsInstallRoute } from './install';
|
||||
import { registerSiemRuleMigrationsInstallTranslatedRoute } from './install_translated';
|
||||
import { registerSiemRuleMigrationsResourceGetMissingRoute } from './resources/missing';
|
||||
import { registerSiemRuleMigrationsPrebuiltRulesRoute } from './get_prebuilt_rules';
|
||||
import { registerSiemRuleMigrationsIntegrationsRoute } from './get_integrations';
|
||||
|
@ -37,7 +36,6 @@ export const registerSiemRuleMigrationsRoutes = (
|
|||
registerSiemRuleMigrationsTranslationStatsRoute(router, logger);
|
||||
registerSiemRuleMigrationsStopRoute(router, logger);
|
||||
registerSiemRuleMigrationsInstallRoute(router, logger);
|
||||
registerSiemRuleMigrationsInstallTranslatedRoute(router, logger);
|
||||
registerSiemRuleMigrationsIntegrationsRoute(router, logger);
|
||||
|
||||
registerSiemRuleMigrationsResourceUpsertRoute(router, logger);
|
||||
|
|
|
@ -49,17 +49,16 @@ export const registerSiemRuleMigrationsInstallRoute = (
|
|||
const savedObjectsClient = ctx.core.savedObjects.client;
|
||||
const rulesClient = await ctx.alerting.getRulesClient();
|
||||
|
||||
await installTranslated({
|
||||
const installed = await installTranslated({
|
||||
migrationId,
|
||||
ids,
|
||||
enabled,
|
||||
securitySolutionContext,
|
||||
savedObjectsClient,
|
||||
rulesClient,
|
||||
logger,
|
||||
});
|
||||
|
||||
return res.ok({ body: { installed: true } });
|
||||
return res.ok({ body: { installed } });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return res.badRequest({ body: err.message });
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* 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<IKibanaResponse<InstallTranslatedMigrationRulesResponse>> => {
|
||||
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 = await ctx.alerting.getRulesClient();
|
||||
|
||||
await installTranslated({
|
||||
migrationId,
|
||||
enabled: false,
|
||||
securitySolutionContext,
|
||||
savedObjectsClient,
|
||||
rulesClient,
|
||||
logger,
|
||||
});
|
||||
|
||||
return res.ok({ body: { installed: true } });
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return res.badRequest({ body: err.message });
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import { getErrorMessage } from '../../../../../utils/error_helpers';
|
||||
import type { UpdateRuleMigrationData } from '../../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import { initPromisePool } from '../../../../../utils/promise_pool';
|
||||
import type { SecuritySolutionApiRequestHandlerContext } from '../../../../..';
|
||||
|
@ -16,15 +17,13 @@ import type { IDetectionRulesClient } from '../../../../detection_engine/rule_ma
|
|||
import type { RuleResponse } from '../../../../../../common/api/detection_engine';
|
||||
import type { StoredRuleMigration } from '../../types';
|
||||
import { getPrebuiltRules, getUniquePrebuiltRuleIds } from './prebuilt_rules';
|
||||
import {
|
||||
MAX_CUSTOM_RULES_TO_CREATE_IN_PARALLEL,
|
||||
MAX_TRANSLATED_RULES_TO_INSTALL,
|
||||
} from '../constants';
|
||||
import {
|
||||
convertMigrationCustomRuleToSecurityRulePayload,
|
||||
isMigrationCustomRule,
|
||||
} from '../../../../../../common/siem_migrations/rules/utils';
|
||||
|
||||
const MAX_CUSTOM_RULES_TO_CREATE_IN_PARALLEL = 50;
|
||||
|
||||
const installPrebuiltRules = async (
|
||||
rulesToInstall: StoredRuleMigration[],
|
||||
enabled: boolean,
|
||||
|
@ -32,7 +31,7 @@ const installPrebuiltRules = async (
|
|||
rulesClient: RulesClient,
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
detectionRulesClient: IDetectionRulesClient
|
||||
): Promise<UpdateRuleMigrationData[]> => {
|
||||
): Promise<{ rulesToUpdate: UpdateRuleMigrationData[]; errors: Error[] }> => {
|
||||
// Get required prebuilt rules
|
||||
const prebuiltRulesIds = getUniquePrebuiltRuleIds(rulesToInstall);
|
||||
const prebuiltRules = await getPrebuiltRules(rulesClient, savedObjectsClient, prebuiltRulesIds);
|
||||
|
@ -52,13 +51,16 @@ const installPrebuiltRules = async (
|
|||
}
|
||||
);
|
||||
|
||||
const errors: Error[] = [];
|
||||
|
||||
// Install prebuilt rules
|
||||
// TODO: we need to do an error handling which can happen during the rule installation
|
||||
const { results: newlyInstalledRules } = await createPrebuiltRules(
|
||||
detectionRulesClient,
|
||||
installable
|
||||
const { results: newlyInstalledRules, errors: installPrebuiltRulesErrors } =
|
||||
await createPrebuiltRules(detectionRulesClient, installable);
|
||||
errors.push(
|
||||
...installPrebuiltRulesErrors.map(
|
||||
(err) => new Error(`Error installing prebuilt rule: ${getErrorMessage(err)}`)
|
||||
)
|
||||
);
|
||||
await performTimelinesInstallation(securitySolutionContext);
|
||||
|
||||
const installedRules = [
|
||||
...alreadyInstalledRules,
|
||||
|
@ -81,15 +83,18 @@ const installPrebuiltRules = async (
|
|||
);
|
||||
});
|
||||
|
||||
return rulesToUpdate;
|
||||
return { rulesToUpdate, errors };
|
||||
};
|
||||
|
||||
export const installCustomRules = async (
|
||||
rulesToInstall: StoredRuleMigration[],
|
||||
enabled: boolean,
|
||||
detectionRulesClient: IDetectionRulesClient,
|
||||
logger: Logger
|
||||
): Promise<UpdateRuleMigrationData[]> => {
|
||||
detectionRulesClient: IDetectionRulesClient
|
||||
): Promise<{
|
||||
rulesToUpdate: UpdateRuleMigrationData[];
|
||||
errors: Error[];
|
||||
}> => {
|
||||
const errors: Error[] = [];
|
||||
const rulesToUpdate: UpdateRuleMigrationData[] = [];
|
||||
const createCustomRulesOutcome = await initPromisePool({
|
||||
concurrency: MAX_CUSTOM_RULES_TO_CREATE_IN_PARALLEL,
|
||||
|
@ -113,15 +118,12 @@ export const installCustomRules = async (
|
|||
});
|
||||
},
|
||||
});
|
||||
if (createCustomRulesOutcome.errors) {
|
||||
// TODO: we need to do an error handling which can happen during the rule creation
|
||||
logger.debug(
|
||||
`Failed to create some of the rules because of errors: ${JSON.stringify(
|
||||
createCustomRulesOutcome.errors
|
||||
)}`
|
||||
);
|
||||
}
|
||||
return rulesToUpdate;
|
||||
errors.push(
|
||||
...createCustomRulesOutcome.errors.map(
|
||||
(err) => new Error(`Error installing custom rule: ${getErrorMessage(err)}`)
|
||||
)
|
||||
);
|
||||
return { rulesToUpdate, errors };
|
||||
};
|
||||
|
||||
interface InstallTranslatedProps {
|
||||
|
@ -155,11 +157,6 @@ interface InstallTranslatedProps {
|
|||
* The saved objects client
|
||||
*/
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export const installTranslated = async ({
|
||||
|
@ -169,51 +166,63 @@ export const installTranslated = async ({
|
|||
securitySolutionContext,
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
}: InstallTranslatedProps) => {
|
||||
}: InstallTranslatedProps): Promise<number> => {
|
||||
const detectionRulesClient = securitySolutionContext.getDetectionRulesClient();
|
||||
const ruleMigrationsClient = securitySolutionContext.getSiemRuleMigrationsClient();
|
||||
|
||||
const { data: rulesToInstall } = await ruleMigrationsClient.data.rules.get(migrationId, {
|
||||
filters: { ids, installable: true },
|
||||
from: 0,
|
||||
size: MAX_TRANSLATED_RULES_TO_INSTALL,
|
||||
let installedCount = 0;
|
||||
const installationErrors: Error[] = [];
|
||||
|
||||
// Install rules that matched Elastic prebuilt rules
|
||||
const prebuiltRuleBatches = ruleMigrationsClient.data.rules.searchBatches(migrationId, {
|
||||
filters: { ids, installable: true, prebuilt: 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,
|
||||
enabled,
|
||||
securitySolutionContext,
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
detectionRulesClient
|
||||
);
|
||||
|
||||
const updatedCustomRules = await installCustomRules(
|
||||
customRulesToInstall,
|
||||
enabled,
|
||||
detectionRulesClient,
|
||||
logger
|
||||
);
|
||||
|
||||
const rulesToUpdate: UpdateRuleMigrationData[] = [...updatedPrebuiltRules, ...updatedCustomRules];
|
||||
|
||||
if (rulesToUpdate.length) {
|
||||
let prebuiltRulesToInstall = await prebuiltRuleBatches.next();
|
||||
while (prebuiltRulesToInstall.length) {
|
||||
const { rulesToUpdate, errors } = await installPrebuiltRules(
|
||||
prebuiltRulesToInstall,
|
||||
enabled,
|
||||
securitySolutionContext,
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
detectionRulesClient
|
||||
);
|
||||
installedCount += rulesToUpdate.length;
|
||||
installationErrors.push(...errors);
|
||||
await ruleMigrationsClient.data.rules.update(rulesToUpdate);
|
||||
prebuiltRulesToInstall = await prebuiltRuleBatches.next();
|
||||
}
|
||||
|
||||
let installTimelinesError: string | undefined;
|
||||
if (installedCount > 0) {
|
||||
const { error } = await performTimelinesInstallation(securitySolutionContext);
|
||||
installTimelinesError = error;
|
||||
}
|
||||
|
||||
// Install rules with custom translation
|
||||
const customRuleBatches = ruleMigrationsClient.data.rules.searchBatches(migrationId, {
|
||||
filters: { ids, installable: true, prebuilt: false },
|
||||
});
|
||||
let customRulesToInstall = await customRuleBatches.next();
|
||||
while (customRulesToInstall.length) {
|
||||
const { rulesToUpdate, errors } = await installCustomRules(
|
||||
customRulesToInstall,
|
||||
enabled,
|
||||
detectionRulesClient
|
||||
);
|
||||
installedCount += rulesToUpdate.length;
|
||||
installationErrors.push(...errors);
|
||||
await ruleMigrationsClient.data.rules.update(rulesToUpdate);
|
||||
customRulesToInstall = await customRuleBatches.next();
|
||||
}
|
||||
|
||||
// Throw an error if needed
|
||||
if (installTimelinesError) {
|
||||
throw new Error(`Error installing prepackaged timelines: ${installTimelinesError}`);
|
||||
}
|
||||
if (installationErrors.length) {
|
||||
throw new Error(installationErrors.map((err) => err.message).join());
|
||||
}
|
||||
|
||||
return installedCount;
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { RuleMigrationRetryFilter } from '../../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import type { RuleMigrationFilters } from '../../data/rule_migrations_data_rules_client';
|
||||
import type { RuleMigrationFilters } from '../../../../../../common/siem_migrations/types';
|
||||
|
||||
const RETRY_FILTERS: Record<RuleMigrationRetryFilter, RuleMigrationFilters> = {
|
||||
failed: { failed: true },
|
||||
|
|
|
@ -15,6 +15,7 @@ import type {
|
|||
QueryDslQueryContainer,
|
||||
Duration,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { RuleMigrationFilters } from '../../../../../common/siem_migrations/types';
|
||||
import type { InternalUpdateRuleMigrationData, StoredRuleMigration } from '../types';
|
||||
import {
|
||||
SiemMigrationStatus,
|
||||
|
@ -36,18 +37,6 @@ export type CreateRuleMigrationInput = Omit<
|
|||
export type RuleMigrationDataStats = Omit<RuleMigrationTaskStats, 'status'>;
|
||||
export type RuleMigrationAllDataStats = RuleMigrationDataStats[];
|
||||
|
||||
export interface RuleMigrationFilters {
|
||||
status?: SiemMigrationStatus | SiemMigrationStatus[];
|
||||
ids?: string[];
|
||||
installed?: boolean;
|
||||
installable?: boolean;
|
||||
prebuilt?: boolean;
|
||||
failed?: boolean;
|
||||
fullyTranslated?: boolean;
|
||||
partiallyTranslated?: boolean;
|
||||
untranslatable?: boolean;
|
||||
searchTerm?: string;
|
||||
}
|
||||
export interface RuleMigrationGetOptions {
|
||||
filters?: RuleMigrationFilters;
|
||||
sort?: RuleMigrationSort;
|
||||
|
@ -397,66 +386,55 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
|
||||
private getFilterQuery(
|
||||
migrationId: string,
|
||||
{
|
||||
status,
|
||||
ids,
|
||||
installed,
|
||||
installable,
|
||||
prebuilt,
|
||||
searchTerm,
|
||||
failed,
|
||||
fullyTranslated,
|
||||
partiallyTranslated,
|
||||
untranslatable,
|
||||
}: RuleMigrationFilters = {}
|
||||
filters: RuleMigrationFilters = {}
|
||||
): QueryDslQueryContainer {
|
||||
const filter: QueryDslQueryContainer[] = [{ term: { migration_id: migrationId } }];
|
||||
if (status) {
|
||||
if (Array.isArray(status)) {
|
||||
filter.push({ terms: { status } });
|
||||
if (filters.status) {
|
||||
if (Array.isArray(filters.status)) {
|
||||
filter.push({ terms: { status: filters.status } });
|
||||
} else {
|
||||
filter.push({ term: { status } });
|
||||
filter.push({ term: { status: filters.status } });
|
||||
}
|
||||
}
|
||||
if (ids) {
|
||||
filter.push({ terms: { _id: ids } });
|
||||
if (filters.ids) {
|
||||
filter.push({ terms: { _id: filters.ids } });
|
||||
}
|
||||
if (searchTerm?.length) {
|
||||
filter.push(searchConditions.matchTitle(searchTerm));
|
||||
if (filters.searchTerm?.length) {
|
||||
filter.push(searchConditions.matchTitle(filters.searchTerm));
|
||||
}
|
||||
if (installed === true) {
|
||||
if (filters.installed === true) {
|
||||
filter.push(searchConditions.isInstalled());
|
||||
} else if (installed === false) {
|
||||
} else if (filters.installed === false) {
|
||||
filter.push(searchConditions.isNotInstalled());
|
||||
}
|
||||
if (installable === true) {
|
||||
if (filters.installable === true) {
|
||||
filter.push(...searchConditions.isInstallable());
|
||||
} else if (installable === false) {
|
||||
} else if (filters.installable === false) {
|
||||
filter.push(...searchConditions.isNotInstallable());
|
||||
}
|
||||
if (prebuilt === true) {
|
||||
if (filters.prebuilt === true) {
|
||||
filter.push(searchConditions.isPrebuilt());
|
||||
} else if (prebuilt === false) {
|
||||
} else if (filters.prebuilt === false) {
|
||||
filter.push(searchConditions.isCustom());
|
||||
}
|
||||
if (failed === true) {
|
||||
if (filters.failed === true) {
|
||||
filter.push(searchConditions.isFailed());
|
||||
} else if (failed === false) {
|
||||
} else if (filters.failed === false) {
|
||||
filter.push(searchConditions.isNotFailed());
|
||||
}
|
||||
if (fullyTranslated === true) {
|
||||
if (filters.fullyTranslated === true) {
|
||||
filter.push(searchConditions.isFullyTranslated());
|
||||
} else if (fullyTranslated === false) {
|
||||
} else if (filters.fullyTranslated === false) {
|
||||
filter.push(searchConditions.isNotFullyTranslated());
|
||||
}
|
||||
if (partiallyTranslated === true) {
|
||||
if (filters.partiallyTranslated === true) {
|
||||
filter.push(searchConditions.isPartiallyTranslated());
|
||||
} else if (partiallyTranslated === false) {
|
||||
} else if (filters.partiallyTranslated === false) {
|
||||
filter.push(searchConditions.isNotPartiallyTranslated());
|
||||
}
|
||||
if (untranslatable === true) {
|
||||
if (filters.untranslatable === true) {
|
||||
filter.push(searchConditions.isUntranslatable());
|
||||
} else if (untranslatable === false) {
|
||||
} else if (filters.untranslatable === false) {
|
||||
filter.push(searchConditions.isNotUntranslatable());
|
||||
}
|
||||
return { bool: { filter } };
|
||||
|
|
|
@ -8,16 +8,14 @@
|
|||
import type { AuthenticatedUser, Logger } from '@kbn/core/server';
|
||||
import { AbortError, abortSignalToPromise } from '@kbn/kibana-utils-plugin/server';
|
||||
import type { RunnableConfig } from '@langchain/core/runnables';
|
||||
import type { RuleMigrationFilters } from '../../../../../common/siem_migrations/types';
|
||||
import {
|
||||
SiemMigrationStatus,
|
||||
SiemMigrationTaskStatus,
|
||||
} from '../../../../../common/siem_migrations/constants';
|
||||
import type { RuleMigrationTaskStats } from '../../../../../common/siem_migrations/model/rule_migration.gen';
|
||||
import type { RuleMigrationsDataClient } from '../data/rule_migrations_data_client';
|
||||
import type {
|
||||
RuleMigrationDataStats,
|
||||
RuleMigrationFilters,
|
||||
} from '../data/rule_migrations_data_rules_client';
|
||||
import type { RuleMigrationDataStats } from '../data/rule_migrations_data_rules_client';
|
||||
import type { SiemRuleMigrationsClientDependencies } from '../types';
|
||||
import { getRuleMigrationAgent } from './agent';
|
||||
import type { MigrateRuleState } from './agent/types';
|
||||
|
|
|
@ -123,7 +123,6 @@ import {
|
|||
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';
|
||||
|
@ -1208,27 +1207,6 @@ 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))
|
||||
|
@ -1860,9 +1838,6 @@ export interface InstallMigrationRulesProps {
|
|||
export interface InstallPrepackedTimelinesProps {
|
||||
body: InstallPrepackedTimelinesRequestBodyInput;
|
||||
}
|
||||
export interface InstallTranslatedMigrationRulesProps {
|
||||
params: InstallTranslatedMigrationRulesRequestParamsInput;
|
||||
}
|
||||
export interface ListEntitiesProps {
|
||||
query: ListEntitiesRequestQueryInput;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue