mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details With these changes we two new routes: * `/internal/siem_migrations/rules/install`: allows to install a specific set of migration rules * `/internal/siem_migrations/rules/install_translated`: allows to install all translated rules in specified migration Also we connect these two new API calls with the "Install" button within the "migration rules" table and the "Install translated rules" button on the "SIEM migration rules" page. ### Screenshots https://github.com/user-attachments/assets/29390d07-eab5-4157-8958-1e3f8459db09 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Sergi Massaneda <sergi.massaneda@gmail.com>
This commit is contained in:
parent
8d2e28aef2
commit
07fbb92585
36 changed files with 1361 additions and 161 deletions
|
@ -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<InstallMigrationRulesResponse>({
|
||||
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<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
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -88,6 +88,48 @@ export type GetRuleMigrationStatsRequestParamsInput = z.input<
|
|||
export type GetRuleMigrationStatsResponse = z.infer<typeof GetRuleMigrationStatsResponse>;
|
||||
export const GetRuleMigrationStatsResponse = RuleMigrationTaskStats;
|
||||
|
||||
export type InstallMigrationRulesRequestParams = z.infer<typeof InstallMigrationRulesRequestParams>;
|
||||
export const InstallMigrationRulesRequestParams = z.object({
|
||||
migration_id: NonEmptyString,
|
||||
});
|
||||
export type InstallMigrationRulesRequestParamsInput = z.input<
|
||||
typeof InstallMigrationRulesRequestParams
|
||||
>;
|
||||
|
||||
export type InstallMigrationRulesRequestBody = z.infer<typeof InstallMigrationRulesRequestBody>;
|
||||
export const InstallMigrationRulesRequestBody = z.array(NonEmptyString);
|
||||
export type InstallMigrationRulesRequestBodyInput = z.input<
|
||||
typeof InstallMigrationRulesRequestBody
|
||||
>;
|
||||
|
||||
export type InstallMigrationRulesResponse = z.infer<typeof InstallMigrationRulesResponse>;
|
||||
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<typeof StartRuleMigrationRequestParams>;
|
||||
export const StartRuleMigrationRequestParams = z.object({
|
||||
migration_id: NonEmptyString,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<InstallMigrationRulesResponse> => {
|
||||
return KibanaServices.get().http.fetch<InstallMigrationRulesResponse>(
|
||||
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<InstallTranslatedMigrationRulesResponse> => {
|
||||
return KibanaServices.get().http.fetch<InstallTranslatedMigrationRulesResponse>(
|
||||
replaceParams(SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, { migration_id: migrationId }),
|
||||
{
|
||||
method: 'POST',
|
||||
version: '1',
|
||||
signal,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<GetRuleMigrationResponse>
|
||||
) => {
|
||||
|
@ -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]);
|
||||
};
|
|
@ -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<InstallTranslatedMigrationRulesResponse, Error>
|
||||
) => {
|
||||
const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId);
|
||||
|
||||
return useMutation<InstallTranslatedMigrationRulesResponse, Error>(
|
||||
() => installTranslatedMigrationRules({ migrationId }),
|
||||
{
|
||||
...options,
|
||||
mutationKey: INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY,
|
||||
onSettled: (...args) => {
|
||||
invalidateGetRuleMigrationsQuery();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
|
@ -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<InstallMigrationRulesResponse, Error, string[]>
|
||||
) => {
|
||||
const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId);
|
||||
|
||||
return useMutation<InstallMigrationRulesResponse, Error, string[]>(
|
||||
(ids: string[]) => installMigrationRules({ migrationId, ids }),
|
||||
{
|
||||
...options,
|
||||
mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY,
|
||||
onSettled: (...args) => {
|
||||
invalidateGetRuleMigrationsQuery();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
|
@ -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<BulkActionsProps> = React.memo(
|
||||
({
|
||||
isTableLoading,
|
||||
numberOfTranslatedRules,
|
||||
numberOfSelectedRules,
|
||||
installTranslatedRule,
|
||||
installSelectedRule,
|
||||
}) => {
|
||||
const showInstallTranslatedRulesButton = numberOfTranslatedRules > 0;
|
||||
const showInstallSelectedRulesButton =
|
||||
showInstallTranslatedRulesButton && numberOfSelectedRules > 0;
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
|
||||
{showInstallSelectedRulesButton ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={installSelectedRule}
|
||||
disabled={isTableLoading}
|
||||
data-test-subj="installSelectedRulesButton"
|
||||
aria-label={i18n.INSTALL_SELECTED_ARIA_LABEL}
|
||||
>
|
||||
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
|
||||
{isTableLoading && <EuiLoadingSpinner size="s" />}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
{showInstallTranslatedRulesButton ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
data-test-subj="installTranslatedRulesButton"
|
||||
onClick={installTranslatedRule}
|
||||
disabled={isTableLoading}
|
||||
aria-label={i18n.INSTALL_ALL_ARIA_LABEL}
|
||||
>
|
||||
{i18n.INSTALL_ALL_RULES(numberOfTranslatedRules)}
|
||||
{isTableLoading && <EuiLoadingSpinner size="s" />}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
BulkActions.displayName = 'BulkActions';
|
|
@ -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<FiltersComponentProps> = ({ filterOptions, setF
|
|||
);
|
||||
|
||||
return (
|
||||
<FilterWrapper gutterSize="m" justifyContent="flexEnd" wrap>
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexEnd" wrap>
|
||||
<RuleSearchField
|
||||
initialValue={filterOptions.filter}
|
||||
onSearch={handleOnSearch}
|
||||
placeholder={i18n.SEARCH_PLACEHOLDER}
|
||||
/>
|
||||
</FilterWrapper>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<RulesTableComponentProps> = ({
|
||||
migrationId,
|
||||
openRulePreview,
|
||||
}) => {
|
||||
const { data: ruleMigrations, isLoading } = useGetRuleMigrationsQuery(migrationId);
|
||||
const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ migrationId }) => {
|
||||
const { data: ruleMigrations, isLoading: isDataLoading } = useGetMigrationRules(migrationId);
|
||||
|
||||
const [selectedRuleMigrations, setSelectedRuleMigrations] = useState<RuleMigration[]>([]);
|
||||
|
||||
|
@ -62,10 +57,60 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({
|
|||
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<RulesTableComponentProps> = ({
|
|||
/>
|
||||
)}
|
||||
<EuiSkeletonLoading
|
||||
isLoading={isLoading}
|
||||
isLoading={isDataLoading}
|
||||
loadingContent={
|
||||
<>
|
||||
<EuiSkeletonTitle />
|
||||
|
@ -91,13 +136,22 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({
|
|||
<NoItemsMessage />
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexEnd" wrap>
|
||||
<EuiFlexItem>
|
||||
<Filters filterOptions={filterOptions} setFilterOptions={setFilterOptions} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<BulkActions
|
||||
isTableLoading={isDataLoading || isTableLoading}
|
||||
numberOfTranslatedRules={numberOfTranslatedRules}
|
||||
numberOfSelectedRules={0}
|
||||
installTranslatedRule={installTranslatedRules}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiInMemoryTable
|
||||
loading={isTableLoading}
|
||||
items={filteredRuleMigrations}
|
||||
sorting
|
||||
pagination={{
|
||||
|
@ -117,6 +171,7 @@ const RulesTableComponent: React.FC<RulesTableComponentProps> = ({
|
|||
)
|
||||
}
|
||||
/>
|
||||
{rulePreviewFlyout}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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 (
|
||||
<EuiLink
|
||||
disabled={disableActions}
|
||||
onClick={() => {
|
||||
navigateToApp(APP_UI_ID, {
|
||||
deepLinkId: SecurityPageName.rules,
|
||||
path: getRuleDetailsUrl(ruleId),
|
||||
});
|
||||
}}
|
||||
data-test-subj="viewRule"
|
||||
>
|
||||
{i18n.ACTIONS_VIEW_LABEL}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (migrationRule.status === 'failed') {
|
||||
return (
|
||||
<EuiLink disabled={disableActions} onClick={() => {}} data-test-subj="restartRule">
|
||||
{i18n.ACTIONS_RESTART_LABEL}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
if (migrationRule.translation_result === 'full') {
|
||||
return (
|
||||
<EuiLink
|
||||
disabled={disableActions}
|
||||
onClick={() => {
|
||||
installMigrationRule(migrationRule);
|
||||
}}
|
||||
data-test-subj="installRule"
|
||||
>
|
||||
{i18n.ACTIONS_INSTALL_LABEL}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiLink
|
||||
disabled={disableActions}
|
||||
onClick={() => {
|
||||
openMigrationRulePreview(migrationRule);
|
||||
}}
|
||||
data-test-subj="editRule"
|
||||
>
|
||||
{i18n.ACTIONS_EDIT_LABEL}
|
||||
</EuiLink>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<ActionName
|
||||
disableActions={disableActions}
|
||||
migrationRule={migrationRule}
|
||||
openMigrationRulePreview={openMigrationRulePreview}
|
||||
installMigrationRule={installMigrationRule}
|
||||
/>
|
||||
);
|
||||
},
|
||||
width: '10%',
|
||||
align: 'center',
|
||||
};
|
||||
};
|
|
@ -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<RuleMigration>;
|
|
@ -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';
|
|
@ -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 (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
openMigrationRulePreview(rule);
|
||||
}}
|
||||
data-test-subj="ruleName"
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
);
|
||||
};
|
||||
|
||||
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) => (
|
||||
<Name name={value} rule={rule} openMigrationRulePreview={openMigrationRulePreview} />
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
width: '40%',
|
||||
align: 'left',
|
||||
};
|
||||
};
|
|
@ -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: () => (
|
||||
<EuiText data-test-subj="riskScore" size="s">
|
||||
{DEFAULT_TRANSLATION_RISK_SCORE}
|
||||
</EuiText>
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
width: '75px',
|
||||
};
|
||||
};
|
|
@ -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) => <SeverityBadge value={value ?? DEFAULT_TRANSLATION_SEVERITY} />,
|
||||
sortable: ({ elastic_rule: elasticRule }: RuleMigration) =>
|
||||
getNormalizedSeverity((elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY),
|
||||
truncateText: true,
|
||||
width: '12%',
|
||||
};
|
||||
};
|
|
@ -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) => (
|
||||
<StatusBadge value={value} installedRuleId={rule.elastic_rule?.id} />
|
||||
),
|
||||
sortable: false,
|
||||
truncateText: true,
|
||||
width: '12%',
|
||||
};
|
||||
};
|
|
@ -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',
|
||||
}
|
||||
);
|
|
@ -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 {
|
||||
|
|
|
@ -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<RuleMigration>;
|
||||
|
||||
interface RuleNameProps {
|
||||
name: string;
|
||||
rule: RuleMigration;
|
||||
openRulePreview: (rule: RuleMigration) => void;
|
||||
}
|
||||
|
||||
const RuleName = ({ name, rule, openRulePreview }: RuleNameProps) => {
|
||||
return (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
openRulePreview(rule);
|
||||
}}
|
||||
data-test-subj="ruleName"
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
);
|
||||
};
|
||||
|
||||
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) => (
|
||||
<RuleName name={value} rule={rule} openRulePreview={openRulePreview} />
|
||||
),
|
||||
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) => (
|
||||
<StatusBadge value={value} installedRuleId={rule.elastic_rule?.id} />
|
||||
),
|
||||
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: () => (
|
||||
<EuiText data-test-subj="riskScore" size="s">
|
||||
{DEFAULT_TRANSLATION_RISK_SCORE}
|
||||
</EuiText>
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
width: '75px',
|
||||
},
|
||||
{
|
||||
field: 'elastic_rule.severity',
|
||||
name: rulesI18n.COLUMN_SEVERITY,
|
||||
render: (value?: Severity) => (
|
||||
<SeverityBadge value={value ?? DEFAULT_TRANSLATION_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]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
|
@ -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 });
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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 });
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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 });
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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<RulesMigrationPageProps> = 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 <UnknownMigration />;
|
||||
}
|
||||
return <RulesTable migrationId={migrationId} openRulePreview={openRulePreview} />;
|
||||
}, [migrationId, migrationsIds, openRulePreview]);
|
||||
return <RulesTable migrationId={migrationId} />;
|
||||
}, [migrationId, migrationsIds]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -108,7 +94,6 @@ export const RulesPage: React.FC<RulesMigrationPageProps> = React.memo(
|
|||
}
|
||||
loadedContent={content}
|
||||
/>
|
||||
{rulePreviewFlyout}
|
||||
</SecuritySolutionPageWrapper>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<IKibanaResponse<InstallMigrationRulesResponse>> => {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
|
@ -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<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 = 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 });
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
|
@ -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<UpdateRuleMigrationInput[]> => {
|
||||
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<UpdateRuleMigrationInput[]> => {
|
||||
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);
|
||||
}
|
||||
};
|
|
@ -34,6 +34,13 @@ export type UpdateRuleMigrationInput = { elastic_rule?: Partial<ElasticRule> } &
|
|||
export type RuleMigrationDataStats = Omit<RuleMigrationTaskStats, 'status'>;
|
||||
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<StoredRuleMigration[]> {
|
||||
async get(filters: RuleMigrationFilterOptions): Promise<StoredRuleMigration[]> {
|
||||
const index = await this.getIndexName();
|
||||
const query = this.getFilterQuery(migrationId);
|
||||
const query = this.getFilterQuery(filters);
|
||||
|
||||
const storedRuleMigrations = await this.esClient
|
||||
.search<RuleMigration>({ index, query, sort: '_doc' })
|
||||
|
@ -123,7 +130,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
*/
|
||||
async takePending(migrationId: string, size: number): Promise<StoredRuleMigration[]> {
|
||||
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<RuleMigration>({ index, query, sort: '_doc', size })
|
||||
|
@ -202,7 +209,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
|
|||
{ refresh = false }: { refresh?: boolean } = {}
|
||||
): Promise<void> {
|
||||
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<RuleMigrationDataStats> {
|
||||
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 } };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue