From 978b3d291b436ccc9a294fbf7ff19ef565189158 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Thu, 3 Jul 2025 11:24:30 +0200 Subject: [PATCH 1/3] [Security Solution] Stop showing ML rule installation and upgrade errors on Basic license (#224676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Resolves: https://github.com/elastic/kibana/issues/197246** **Resolves: https://github.com/elastic/kibana/issues/190753** **Flaky test runner**: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/8483 ## Summary This PR addresses an issue where users with Basic or Essentials licenses encountered errors when attempting to install ML rules. This problem arose because Basic and Essentials licenses do not permit the installation or upgrading of ML rules. The PR resolves this by excluding ML rules from the installation and upgrade `_review` endpoint responses for users with insufficient license. ## Changes - `installation/_review` and `upgrade/_review` don't return ML rules for users on Basic/Essentials. - `prebuilt_rules/status` does not include ML rules in the count of rules to install or upgrade. - `installation/_perform` and `upgrade/_perform` now skip ML rules when called with `ALL_RULES` mode if the license is insufficient. - Added API integration tests for installing and upgrading prebuilt rules on Basic and Essentials, as well as for checking their status. - Now, installation and upgrade errors are displayed in a separate "red" toast if any rules fail to install or upgrade. - Added tests for toasts as well. ## Screenshots and screen recordings ### Installing all prebuilt rules
Click to see screen recordings Before https://github.com/user-attachments/assets/4e68d4c8-5523-495f-9157-41a5f037d261 After https://github.com/user-attachments/assets/465a2bc0-fd89-49cc-9bdb-78d45a6545ea
### Upgrading all prebuilt rules
Click to see screen recordings Before https://github.com/user-attachments/assets/6e7e4299-2348-43b3-b5f6-ed31cf350a69 After https://github.com/user-attachments/assets/84303135-2ed0-4014-a677-d182f2a105c9
### Errors during installation (unrelated to ML rules)
Click to see screenshots **Before: a green toast with combined success and error text** installation_toast_before **After: two toasts – one for success, one for errors** installation_toast_after_1 installation_toast_after_2
--------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../ftr_security_serverless_configs.yml | 1 + .buildkite/ftr_security_stateful_configs.yml | 1 + .../perform_rule_upgrade_route.test.ts | 2 +- .../public/common/components/utils.ts | 30 +++ .../logic/prebuilt_rules/translations.ts | 8 +- .../use_perform_rule_install.ts | 55 +++-- .../use_perform_rule_upgrade.ts | 33 ++- .../components/rule_import_modal/utils.ts | 20 +- .../get_prebuilt_rules_status_route.ts | 35 ++- .../perform_rule_installation_handler.ts | 11 +- .../perform_rule_upgrade_handler.ts | 18 +- .../review_rule_installation_handler.ts | 18 +- .../review_rule_upgrade_handler.ts | 28 ++- .../prebuilt_rules/logic/basic_rule_info.ts | 13 ++ .../prebuilt_rule_assets_client.ts | 9 +- .../prebuilt_rule_objects_client.ts | 18 +- .../prebuilt_rules/logic/utils.ts | 52 +++++ .../routes/__mocks__/request_context.ts | 3 + .../server/request_context_factory.ts | 8 + .../plugins/security_solution/server/types.ts | 2 + .../configs/ess_basic_license.config.ts | 25 +++ .../serverless_essentials_tier.config.ts | 16 ++ .../prebuilt_rules/ml_disabled/index.ts | 18 ++ .../ml_disabled/perform_installation/index.ts | 12 ++ .../perform_installation.ts | 77 +++++++ .../ml_disabled/perform_upgrade/index.ts | 12 ++ .../perform_upgrade/perform_upgrade.ts | 186 ++++++++++++++++ .../ml_disabled/review_installation/index.ts | 12 ++ .../review_installation.ts | 49 +++++ .../ml_disabled/review_upgrade/index.ts | 12 ++ .../review_upgrade/review_upgrade.ts | 128 +++++++++++ .../ml_disabled/status/index.ts | 12 ++ .../ml_disabled/status/status.ts | 67 ++++++ .../prebuilt_rules/ml_disabled/utils.ts | 62 ++++++ .../utils/rules/create_rule_saved_object.ts | 9 +- ...t_rule_with_legacy_investigation_fields.ts | 199 +++++++----------- .../review_install_prebuilt_rules.ts | 2 +- .../tsconfig.json | 26 ++- .../install_update_error_handling.cy.ts | 32 +++ .../cypress/tasks/prebuilt_rules.ts | 109 ++++++++-- 40 files changed, 1189 insertions(+), 241 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/basic_rule_info.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/ess_basic_license.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/serverless_essentials_tier.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/perform_upgrade.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/review_installation.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/review_upgrade.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/utils.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 90911fadac914..66277886d3f19 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -79,6 +79,7 @@ disabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/diffable_rule_fields/common_fields/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/diffable_rule_fields/type_specific_fields/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/serverless_essentials_tier.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/serverless_essentials_tier.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml index c810b4f063571..7901318dc75db 100644 --- a/.buildkite/ftr_security_stateful_configs.yml +++ b/.buildkite/ftr_security_stateful_configs.yml @@ -65,6 +65,7 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/diffable_rule_fields/common_fields/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/diffable_rule_fields/type_specific_fields/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/ess_basic_license.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/ess_basic_license.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts index 7f7a64006adbf..0ab96975ffe47 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts @@ -261,7 +261,7 @@ describe('Perform Rule Upgrade Route Schemas', () => { ); }); - test('rejects paylaod with missing rules array', () => { + test('rejects payload with missing rules array', () => { const invalid = { ...validRequest, rules: undefined }; const result = UpgradeSpecificRulesRequest.safeParse(invalid); expectParseError(result); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/utils.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/utils.ts index abe74b8830bd3..4c1c7afb10fa4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/utils.ts @@ -10,6 +10,7 @@ import { useMemo, useState } from 'react'; import useResizeObserver from 'use-resize-observer/polyfilled'; import { niceTimeFormatByDay, timeFormatter } from '@elastic/charts'; import moment from 'moment-timezone'; +import type { IToasts } from '@kbn/core/public'; export const getDaysDiff = (minDate: moment.Moment, maxDate: moment.Moment) => { const diff = maxDate.diff(minDate, 'days'); @@ -35,3 +36,32 @@ export const useThrottledResizeObserver = (wait = 100) => { return { ref, ...size }; }; + +/** + * Displays an error toast with a specified title and a short message. + * Also allows to set a detailed message that will appear in the modal when user clicks the "See full error" button. + * + * @param title The title of the toast notification. Appears in both the toast header and the modal header. + * @param shortMessage An optional short message. Appears under toast header. + * @param fullMessage The full error message. Appears in the modal when user clicks the "See full error" button. + * @param toasts The toasts service instance. + */ +export function showErrorToast({ + title, + shortMessage, + fullMessage, + toasts, +}: { + title: string; + shortMessage?: string; + fullMessage: string; + toasts: IToasts; +}) { + const error = new Error('Error details'); + error.stack = fullMessage; + toasts.addError(error, { + title, + // Fall back to a space to ensure that the toast component does not render its default message + toastMessage: shortMessage ?? ' ', + }); +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts index 87580af582a16..6d061696515de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/translations.ts @@ -16,7 +16,7 @@ export const RULE_INSTALLATION_FAILED = i18n.translate( export const INSTALL_RULE_SUCCESS = (succeeded: number) => i18n.translate('xpack.securitySolution.detectionEngine.prebuiltRules.toast.installRuleSuccess', { - defaultMessage: '{succeeded, plural, one {# rule} other {# rules}} installed successfully.', + defaultMessage: '{succeeded, plural, one {# rule} other {# rules}} installed successfully', values: { succeeded }, }); @@ -29,7 +29,7 @@ export const INSTALL_RULE_SKIPPED = (skipped: number) => export const INSTALL_RULE_FAILED = (failed: number) => i18n.translate('xpack.securitySolution.detectionEngine.prebuiltRules.toast.installRuleFailed', { - defaultMessage: '{failed, plural, one {# rule} other {# rules}} failed to install.', + defaultMessage: '{failed, plural, one {# rule} other {# rules}} failed to install', values: { failed }, }); @@ -42,7 +42,7 @@ export const RULE_UPGRADE_FAILED = i18n.translate( export const UPGRADE_RULE_SUCCESS = (succeeded: number) => i18n.translate('xpack.securitySolution.detectionEngine.prebuiltRules.toast.upgradeRuleSuccess', { - defaultMessage: '{succeeded, plural, one {# rule} other {# rules}} updated successfully.', + defaultMessage: '{succeeded, plural, one {# rule} other {# rules}} updated successfully', values: { succeeded }, }); @@ -55,6 +55,6 @@ export const UPGRADE_RULE_SKIPPED = (skipped: number) => export const UPGRADE_RULE_FAILED = (failed: number) => i18n.translate('xpack.securitySolution.detectionEngine.prebuiltRules.toast.upgradeRuleFailed', { - defaultMessage: '{failed, plural, one {# rule} other {# rules}} failed to update.', + defaultMessage: '{failed, plural, one {# rule} other {# rules}} failed to update', values: { failed }, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_install.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_install.ts index 26e565ce9ff6d..5665499c6652f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_install.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_install.ts @@ -4,49 +4,75 @@ * 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 type { IToasts } from '@kbn/core/public'; +import type { PerformRuleInstallationResponseBody } from '../../../../../common/api/detection_engine'; +import { useToasts } from '../../../../common/lib/kibana'; import { usePerformAllRulesInstallMutation } from '../../api/hooks/prebuilt_rules/use_perform_all_rules_install_mutation'; import { usePerformSpecificRulesInstallMutation } from '../../api/hooks/prebuilt_rules/use_perform_specific_rules_install_mutation'; import * as i18n from './translations'; +import { showErrorToast } from '../../../../common/components/utils'; export const usePerformInstallAllRules = () => { - const { addError, addSuccess } = useAppToasts(); + const toasts = useToasts(); return usePerformAllRulesInstallMutation({ - onError: (err) => { - addError(err, { title: i18n.RULE_INSTALLATION_FAILED }); + onError: (error) => { + handleErrorResponse(error, toasts); }, onSuccess: (result) => { - addSuccess(getSuccessToastMessage(result)); + handleSuccessResponse(result, toasts); }, }); }; export const usePerformInstallSpecificRules = () => { - const { addError, addSuccess } = useAppToasts(); + const toasts = useToasts(); return usePerformSpecificRulesInstallMutation({ - onError: (err) => { - addError(err, { title: i18n.RULE_INSTALLATION_FAILED }); + onError: (error) => { + handleErrorResponse(error, toasts); }, onSuccess: (result) => { - addSuccess(getSuccessToastMessage(result)); + handleSuccessResponse(result, toasts); }, }); }; -const getSuccessToastMessage = (result: { +function handleErrorResponse(error: unknown, toasts: IToasts) { + showErrorToast({ + title: i18n.RULE_INSTALLATION_FAILED, + fullMessage: JSON.stringify(error, null, 2), + toasts, + }); +} + +function handleSuccessResponse(result: PerformRuleInstallationResponseBody, toasts: IToasts) { + const successToastMessage = getSuccessToastMessage(result); + if (successToastMessage) { + toasts.addSuccess(successToastMessage); + } + + if (result.summary.failed > 0) { + showErrorToast({ + title: i18n.INSTALL_RULE_FAILED(result.summary.failed), + fullMessage: JSON.stringify(result.errors, null, 2), + toasts, + }); + } +} + +function getSuccessToastMessage(result: { summary: { total: number; succeeded: number; skipped: number; failed: number; }; -}) => { +}): string { const toastMessages: string[] = []; const { - summary: { succeeded, skipped, failed }, + summary: { succeeded, skipped }, } = result; if (succeeded > 0) { toastMessages.push(i18n.INSTALL_RULE_SUCCESS(succeeded)); @@ -54,8 +80,5 @@ const getSuccessToastMessage = (result: { if (skipped > 0) { toastMessages.push(i18n.INSTALL_RULE_SKIPPED(skipped)); } - if (failed > 0) { - toastMessages.push(i18n.INSTALL_RULE_FAILED(failed)); - } return toastMessages.join(' '); -}; +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts index e1ae2768dacc6..c541828d060a0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/prebuilt_rules/use_perform_rule_upgrade.ts @@ -4,24 +4,42 @@ * 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 { showErrorToast } from '../../../../common/components/utils'; +import { useToasts } from '../../../../common/lib/kibana'; import { usePerformRulesUpgradeMutation } from '../../api/hooks/prebuilt_rules/use_perform_rules_upgrade_mutation'; import * as i18n from './translations'; export const usePerformUpgradeRules = () => { - const { addError, addSuccess } = useAppToasts(); + const toasts = useToasts(); return usePerformRulesUpgradeMutation({ - onError: (err) => { - addError(err, { title: i18n.RULE_UPGRADE_FAILED }); + onError: (error) => { + showErrorToast({ + title: i18n.RULE_UPGRADE_FAILED, + fullMessage: JSON.stringify(error, null, 2), + toasts, + }); }, onSuccess: (result, vars) => { if (vars.dry_run) { // This is a preflight check, no need to show toast return; } - addSuccess(getSuccessToastMessage(result)); + + const successToastMessage = getSuccessToastMessage(result); + if (successToastMessage) { + toasts.addSuccess(getSuccessToastMessage(result)); + } + + if (result.summary.failed > 0) { + showErrorToast({ + title: i18n.UPGRADE_RULE_FAILED(result.summary.failed), + fullMessage: JSON.stringify(result.errors, null, 2), + toasts, + }); + } }, }); }; @@ -36,7 +54,7 @@ const getSuccessToastMessage = (result: { }) => { const toastMessage: string[] = []; const { - summary: { succeeded, skipped, failed }, + summary: { succeeded, skipped }, } = result; if (succeeded > 0) { toastMessage.push(i18n.UPGRADE_RULE_SUCCESS(succeeded)); @@ -44,8 +62,5 @@ const getSuccessToastMessage = (result: { if (skipped > 0) { toastMessage.push(i18n.UPGRADE_RULE_SKIPPED(skipped)); } - if (failed > 0) { - toastMessage.push(i18n.UPGRADE_RULE_FAILED(failed)); - } return toastMessage.join(' '); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_import_modal/utils.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_import_modal/utils.ts index cb17f776572a7..688011267ee8d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_import_modal/utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rule_import_modal/utils.ts @@ -11,6 +11,7 @@ import type { IToasts } from '@kbn/core/public'; import * as i18n from './translations'; import type { ErrorSchema, ImportRulesResponse } from '../../../../../common/api/detection_engine'; +import { showErrorToast } from '../../../../common/components/utils'; export function getFailedConnectorsCount(actionConnectorsErrors: ErrorSchema[]) { const connectorIds = new Set( @@ -46,25 +47,6 @@ function getUserFriendlyConnectorMessages(actionConnectorsErrors: ErrorSchema[]) return mappedErrors; } -function showErrorToast({ - title, - shortMessage, - fullMessage, - toasts, -}: { - title: string; - shortMessage: string; - fullMessage: string; - toasts: IToasts; -}) { - const error = new Error('Error details'); - error.stack = fullMessage; - toasts.addError(error, { - title, - toastMessage: shortMessage, - }); -} - export function showToast({ importResponse, toasts, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts index adccc7ea6488d..e1fa7d9dc13c9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts @@ -12,6 +12,7 @@ import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; +import { excludeLicenseRestrictedRules, getPossibleUpgrades } from '../../logic/utils'; export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -33,11 +34,12 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter const siemResponse = buildSiemResponse(response); try { - const ctx = await context.resolve(['core', 'alerting']); + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); const soClient = ctx.core.savedObjects.client; const rulesClient = await ctx.alerting.getRulesClient(); const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + const mlAuthz = ctx.securitySolution.getMlAuthz(); const currentRuleVersions = await ruleObjectsClient.fetchInstalledRuleVersions(); const latestRuleVersions = await ruleAssetsClient.fetchLatestVersions(); @@ -47,24 +49,39 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter const latestRuleVersionsMap = new Map( latestRuleVersions.map((rule) => [rule.rule_id, rule]) ); - const installableRules = latestRuleVersions.filter( + const allInstallableRules = latestRuleVersions.filter( (rule) => !currentRuleVersionsMap.has(rule.rule_id) ); - const upgradeableRules = currentRuleVersions.filter((rule) => { - const latestVersion = latestRuleVersionsMap.get(rule.rule_id); - return latestVersion != null && rule.version < latestVersion.version; - }); + + const installableRuleAssets = await excludeLicenseRestrictedRules( + allInstallableRules, + mlAuthz + ); + + const upgradableRules = await getPossibleUpgrades( + currentRuleVersions, + latestRuleVersionsMap, + mlAuthz + ); + + const upgradeableRulesTags = upgradableRules.reduce((tags, rule) => { + const ruleTags = currentRuleVersionsMap.get(rule.rule_id)?.tags; + if (ruleTags) { + tags.push(...ruleTags); + } + return tags; + }, []); const body: GetPrebuiltRulesStatusResponseBody = { stats: { num_prebuilt_rules_installed: currentRuleVersions.length, - num_prebuilt_rules_to_install: installableRules.length, - num_prebuilt_rules_to_upgrade: upgradeableRules.length, + num_prebuilt_rules_to_install: installableRuleAssets.length, + num_prebuilt_rules_to_upgrade: upgradableRules.length, num_prebuilt_rules_total_in_package: latestRuleVersions.length, }, aggregated_fields: { upgradeable_rules: { - tags: [...new Set(upgradeableRules.flatMap((rule) => rule.tags))], + tags: [...new Set(upgradeableRulesTags)], }, }, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_handler.ts index f156e06d3e5e8..3fa0aba78503e 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_handler.ts @@ -22,6 +22,7 @@ import { createPrebuiltRules } from '../../logic/rule_objects/create_prebuilt_ru import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; import type { RuleSignatureId, RuleVersion } from '../../../../../../common/api/detection_engine'; +import { excludeLicenseRestrictedRules } from '../../logic/utils'; export const performRuleInstallationHandler = async ( context: SecuritySolutionRequestHandlerContext, @@ -39,6 +40,7 @@ export const performRuleInstallationHandler = async ( const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); const exceptionsListClient = ctx.securitySolution.getExceptionListClient(); + const mlAuthz = ctx.securitySolution.getMlAuthz(); const { mode } = request.body; @@ -55,10 +57,9 @@ export const performRuleInstallationHandler = async ( currentRuleVersions.map((version) => [version.rule_id, version]) ); - const allInstallableRules = allLatestVersions.filter((latestVersion) => { - const currentVersion = currentRuleVersionsMap.get(latestVersion.rule_id); - return !currentVersion; - }); + const allInstallableRules = allLatestVersions.filter( + (latestVersion) => !currentRuleVersionsMap.has(latestVersion.rule_id) + ); const ruleInstallQueue: Array<{ rule_id: RuleSignatureId; @@ -95,7 +96,7 @@ export const performRuleInstallationHandler = async ( ruleInstallQueue.push(rule); }); } else if (mode === 'ALL_RULES') { - ruleInstallQueue.push(...allInstallableRules); + ruleInstallQueue.push(...(await excludeLicenseRestrictedRules(allInstallableRules, mlAuthz))); } const BATCH_SIZE = 100; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts index 3a9a88870f8f2..bf62755544820 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_handler.ts @@ -41,6 +41,7 @@ import { zipRuleVersions } from '../../logic/rule_versions/zip_rule_versions'; import type { RuleVersions } from '../../logic/diff/calculate_rule_diff'; import { calculateRuleDiff } from '../../logic/diff/calculate_rule_diff'; import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; +import { getPossibleUpgrades } from '../../logic/utils'; export const performRuleUpgradeHandler = async ( context: SecuritySolutionRequestHandlerContext, @@ -56,6 +57,7 @@ export const performRuleUpgradeHandler = async ( const detectionRulesClient = ctx.securitySolution.getDetectionRulesClient(); const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + const mlAuthz = ctx.securitySolution.getMlAuthz(); const { isRulesCustomizationEnabled } = detectionRulesClient.getRuleCustomizationStatus(); const defaultPickVersion = isRulesCustomizationEnabled @@ -92,15 +94,13 @@ export const performRuleUpgradeHandler = async ( filter, }); - allCurrentVersions.forEach((current) => { - const latest = latestVersionsMap.get(current.rule_id); - if (latest && latest.version > current.version) { - ruleUpgradeQueue.push({ - rule_id: current.rule_id, - version: latest.version, - }); - } - }); + const upgradableRules = await getPossibleUpgrades( + allCurrentVersions, + latestVersionsMap, + mlAuthz + ); + + ruleUpgradeQueue.push(...upgradableRules); } else if (mode === ModeEnum.SPECIFIC_RULES) { ruleUpgradeQueue.push(...request.body.rules); } diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts index 0bf1ee5cfc80a..ea9c0e3fc65a3 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_handler.ts @@ -17,6 +17,7 @@ import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; +import { excludeLicenseRestrictedRules } from '../../logic/utils'; export const reviewRuleInstallationHandler = async ( context: SecuritySolutionRequestHandlerContext, @@ -26,11 +27,12 @@ export const reviewRuleInstallationHandler = async ( const siemResponse = buildSiemResponse(response); try { - const ctx = await context.resolve(['core', 'alerting']); + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); const soClient = ctx.core.savedObjects.client; const rulesClient = await ctx.alerting.getRulesClient(); const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + const mlAuthz = ctx.securitySolution.getMlAuthz(); const allLatestVersions = await ruleAssetsClient.fetchLatestVersions(); const currentRuleVersions = await ruleObjectsClient.fetchInstalledRuleVersions(); @@ -38,11 +40,15 @@ export const reviewRuleInstallationHandler = async ( currentRuleVersions.map((version) => [version.rule_id, version]) ); - const installableRules = allLatestVersions.filter((latestVersion) => { - const currentVersion = currentRuleVersionsMap.get(latestVersion.rule_id); - return !currentVersion; - }); - const installableRuleAssets = await ruleAssetsClient.fetchAssetsByVersion(installableRules); + const allInstallableRules = allLatestVersions.filter( + (latestVersion) => !currentRuleVersionsMap.has(latestVersion.rule_id) + ); + + const nonInstalledRuleAssets = await ruleAssetsClient.fetchAssetsByVersion(allInstallableRules); + const installableRuleAssets = await excludeLicenseRestrictedRules( + nonInstalledRuleAssets, + mlAuthz + ); const body: ReviewRuleInstallationResponseBody = { stats: calculateRuleStats(installableRuleAssets), diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts index 05a52dbf0ed41..46f3df1a4ee5b 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_handler.ts @@ -20,9 +20,11 @@ import type { IPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import type { IPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; -import type { RuleVersionSpecifier } from '../../logic/rule_versions/rule_version_specifier'; +import { type RuleVersionSpecifier } from '../../logic/rule_versions/rule_version_specifier'; import { zipRuleVersions } from '../../logic/rule_versions/zip_rule_versions'; import { calculateRuleUpgradeInfo } from './calculate_rule_upgrade_info'; +import type { MlAuthz } from '../../../../machine_learning/authz'; +import { getPossibleUpgrades } from '../../logic/utils'; const DEFAULT_SORT: ReviewRuleUpgradeSort = { field: 'name', @@ -38,15 +40,17 @@ export const reviewRuleUpgradeHandler = async ( const { page = 1, per_page: perPage = 20, sort = DEFAULT_SORT, filter } = request.body ?? {}; try { - const ctx = await context.resolve(['core', 'alerting']); + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); const soClient = ctx.core.savedObjects.client; const rulesClient = await ctx.alerting.getRulesClient(); const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + const mlAuthz = ctx.securitySolution.getMlAuthz(); const { diffResults, totalUpgradeableRules } = await calculateUpgradeableRulesDiff({ ruleAssetsClient, ruleObjectsClient, + mlAuthz, page, perPage, sort, @@ -79,6 +83,7 @@ export const reviewRuleUpgradeHandler = async ( interface CalculateUpgradeableRulesDiffArgs { ruleAssetsClient: IPrebuiltRuleAssetsClient; ruleObjectsClient: IPrebuiltRuleObjectsClient; + mlAuthz: MlAuthz; page: number; perPage: number; sort: ReviewRuleUpgradeSort; @@ -88,6 +93,7 @@ interface CalculateUpgradeableRulesDiffArgs { async function calculateUpgradeableRulesDiff({ ruleAssetsClient, ruleObjectsClient, + mlAuthz, page, perPage, sort, @@ -107,15 +113,19 @@ async function calculateUpgradeableRulesDiff({ sortField: sort.field, sortOrder: sort.order, }); - const upgradeableRuleIds = currentRuleVersions - .filter((rule) => { - const targetVersion = latestVersionsMap.get(rule.rule_id); - return targetVersion != null && rule.version < targetVersion.version; - }) + + const upgradeableRules = await getPossibleUpgrades( + currentRuleVersions, + latestVersionsMap, + mlAuthz + ); + + const totalUpgradeableRules = upgradeableRules.length; + + const pagedRuleIds = upgradeableRules + .slice((page - 1) * perPage, page * perPage) .map((rule) => rule.rule_id); - const totalUpgradeableRules = upgradeableRuleIds.length; - const pagedRuleIds = upgradeableRuleIds.slice((page - 1) * perPage, page * perPage); const currentRules = await ruleObjectsClient.fetchInstalledRulesByIds({ ruleIds: pagedRuleIds, sortField: sort.field, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/basic_rule_info.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/basic_rule_info.ts new file mode 100644 index 0000000000000..b82736f117655 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/basic_rule_info.ts @@ -0,0 +1,13 @@ +/* + * 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 { Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleVersionSpecifier } from './rule_versions/rule_version_specifier'; + +export interface BasicRuleInfo extends RuleVersionSpecifier { + type: Type; +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts index 1a1a23cca785c..685c19c3d835d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client.ts @@ -18,6 +18,7 @@ import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_as import { validatePrebuiltRuleAssets } from './prebuilt_rule_assets_validation'; import { PREBUILT_RULE_ASSETS_SO_TYPE } from './prebuilt_rule_assets_type'; import type { RuleVersionSpecifier } from '../rule_versions/rule_version_specifier'; +import type { BasicRuleInfo } from '../basic_rule_info'; const RULE_ASSET_ATTRIBUTES = `${PREBUILT_RULE_ASSETS_SO_TYPE}.attributes`; const MAX_PREBUILT_RULES_COUNT = 10_000; @@ -27,7 +28,7 @@ const ES_MAX_CONCURRENT_REQUESTS = 2; export interface IPrebuiltRuleAssetsClient { fetchLatestAssets: () => Promise; - fetchLatestVersions(ruleIds?: string[]): Promise; + fetchLatestVersions(ruleIds?: string[]): Promise; fetchAssetsByVersion(versions: RuleVersionSpecifier[]): Promise; } @@ -79,7 +80,7 @@ export const createPrebuiltRuleAssetsClient = ( }); }, - fetchLatestVersions: (ruleIds?: string[]): Promise => { + fetchLatestVersions: (ruleIds?: string[]): Promise => { return withSecuritySpan('IPrebuiltRuleAssetsClient.fetchLatestVersions', async () => { if (ruleIds && ruleIds.length === 0) { return []; @@ -114,6 +115,7 @@ export const createPrebuiltRuleAssetsClient = ( _source: [ `${PREBUILT_RULE_ASSETS_SO_TYPE}.rule_id`, `${PREBUILT_RULE_ASSETS_SO_TYPE}.version`, + `${PREBUILT_RULE_ASSETS_SO_TYPE}.type`, ], }, }, @@ -141,9 +143,10 @@ export const createPrebuiltRuleAssetsClient = ( const latestVersions = buckets.map((bucket) => { const hit = bucket.latest_version.hits.hits[0]; const soAttributes = hit._source[PREBUILT_RULE_ASSETS_SO_TYPE]; - const versionInfo: RuleVersionSpecifier = { + const versionInfo: BasicRuleInfo = { rule_id: soAttributes.rule_id, version: soAttributes.version, + type: soAttributes.type, }; return versionInfo; }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts index 2bf7cf5c44364..5bce3d8ee59cb 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client.ts @@ -7,9 +7,11 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { + RuleObjectId, RuleResponse, RuleSignatureId, RuleTagArray, + RuleVersion, } from '../../../../../../common/api/detection_engine/model/rule_schema'; import { withSecuritySpan } from '../../../../../utils/with_security_span'; import { findRules } from '../../../rule_management/logic/search/find_rules'; @@ -21,7 +23,6 @@ import type { SortOrder, } from '../../../../../../common/api/detection_engine'; import { MAX_PREBUILT_RULES_COUNT } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; -import type { RuleVersionSpecifier } from '../rule_versions/rule_version_specifier'; interface FetchAllInstalledRulesArgs { page?: number; @@ -49,15 +50,20 @@ interface FetchInstalledRulesByIdsArgs { sortOrder?: SortOrder; } +interface RuleSummary { + id: RuleObjectId; + rule_id: RuleSignatureId; + version: RuleVersion; + tags: RuleTagArray; +} + export interface IPrebuiltRuleObjectsClient { fetchInstalledRulesByIds(args: FetchInstalledRulesByIdsArgs): Promise; fetchInstalledRules(args?: FetchAllInstalledRulesArgs): Promise; fetchInstalledRuleVersionsByIds( args: FetchInstalledRuleVersionsByIdsArgs - ): Promise>; - fetchInstalledRuleVersions( - args?: FetchAllInstalledRuleVersionsArgs - ): Promise>; + ): Promise; + fetchInstalledRuleVersions(args?: FetchAllInstalledRuleVersionsArgs): Promise; } export const createPrebuiltRuleObjectsClient = ( @@ -125,6 +131,7 @@ export const createPrebuiltRuleObjectsClient = ( fields: ['params.ruleId', 'params.version', 'tags'], }); return rulesData.data.map((rule) => ({ + id: rule.id, rule_id: rule.params.ruleId, version: rule.params.version, tags: rule.tags, @@ -151,6 +158,7 @@ export const createPrebuiltRuleObjectsClient = ( fields: ['params.ruleId', 'params.version', 'tags'], }); return rulesData.data.map((rule) => ({ + id: rule.id, rule_id: rule.params.ruleId, version: rule.params.version, tags: rule.tags, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.ts index 9c1b3fdff54b3..78706f6060bb9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.ts @@ -5,7 +5,11 @@ * 2.0. */ +import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { MlAuthz } from '../../../machine_learning/authz'; import type { RuleAlertType } from '../../rule_schema'; +import type { RuleVersionSpecifier } from './rule_versions/rule_version_specifier'; +import type { BasicRuleInfo } from './basic_rule_info'; /** * Converts an array of rules to a Map with rule IDs as keys @@ -15,3 +19,51 @@ import type { RuleAlertType } from '../../rule_schema'; */ export const rulesToMap = (rules: RuleAlertType[]) => new Map(rules.map((rule) => [rule.params.ruleId, rule])); + +/** + * Excludes rules that are not allowed under the current license. + * + * @param rules The array of rule objects to filter + * @param mlAuthz Machine Learning authorization object + * @returns A new array containing only the rules that are allowed under the current license + */ +export async function excludeLicenseRestrictedRules( + rules: T[], + mlAuthz: MlAuthz +): Promise { + const validationResults = await Promise.all( + rules.map((rule) => mlAuthz.validateRuleType(rule.type)) + ); + + return rules.filter((_rule, index) => validationResults[index].valid); +} + +function getUpgradeTargets( + currentRules: RuleVersionSpecifier[], + targetRulesMap: Map +): BasicRuleInfo[] { + return currentRules.reduce((allUpgradableRules, currentRule) => { + const targetRule = targetRulesMap.get(currentRule.rule_id); + if (targetRule && currentRule.version < targetRule.version) { + allUpgradableRules.push(targetRule); + } + return allUpgradableRules; + }, []); +} + +/** + * Given current and a target rules, returns a list of possible upgrade targets. + * + * @param currentRules The list of rules currently installed. + * @param targetRulesMap A map of the latest available rule versions, with rule_id as the key. + * @param mlAuthz Machine Learning authorization object + * @returns An array of target rule version specifiers. + */ +export function getPossibleUpgrades( + currentRules: RuleVersionSpecifier[], + targetRulesMap: Map, + mlAuthz: MlAuthz +): Promise { + const upgradeTargets = getUpgradeTargets(currentRules, targetRulesMap); + return excludeLicenseRestrictedRules(upgradeTargets, mlAuthz); +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 7c0904e49283c..8308073a64827 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -173,6 +173,9 @@ const createSecuritySolutionRequestContextMock = ( getSiemRuleMigrationsClient: jest.fn(() => clients.siemRuleMigrationsClient), getInferenceClient: jest.fn(() => clients.getInferenceClient()), getAssetInventoryClient: jest.fn(() => clients.assetInventoryDataClient), + getMlAuthz: jest.fn(() => ({ + validateRuleType: jest.fn(async () => ({ valid: true, message: undefined })), + })), }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts b/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts index bf017b0544fe0..813f1e1f7cd66 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts @@ -284,6 +284,14 @@ export class RequestContextFactory implements IRequestContextFactory { experimentalFeatures: config.experimentalFeatures, }); }), + getMlAuthz: memoize(() => { + return buildMlAuthz({ + license: licensing.license, + ml: plugins.ml, + request, + savedObjectsClient: coreContext.savedObjects.client, + }); + }), }; } } diff --git a/x-pack/solutions/security/plugins/security_solution/server/types.ts b/x-pack/solutions/security/plugins/security_solution/server/types.ts index bc1ad512e07a1..5cf9045ab2d88 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/types.ts @@ -40,6 +40,7 @@ import type { EntityStoreDataClient } from './lib/entity_analytics/entity_store/ import type { SiemRuleMigrationsClient } from './lib/siem_migrations/rules/siem_rule_migrations_service'; import type { AssetInventoryDataClient } from './lib/asset_inventory/asset_inventory_data_client'; import type { ApiKeyManager } from './lib/entity_analytics/entity_store/auth/api_key'; +import type { MlAuthz } from './lib/machine_learning/authz'; export { AppClient }; export interface SecuritySolutionApiRequestHandlerContext { @@ -67,6 +68,7 @@ export interface SecuritySolutionApiRequestHandlerContext { getSiemRuleMigrationsClient: () => SiemRuleMigrationsClient; getInferenceClient: () => InferenceClient; getAssetInventoryClient: () => AssetInventoryDataClient; + getMlAuthz: () => MlAuthz; } export type SecuritySolutionRequestHandlerContext = CustomRequestHandlerContext<{ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/ess_basic_license.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/ess_basic_license.config.ts new file mode 100644 index 0000000000000..9862899071b15 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/ess_basic_license.config.ts @@ -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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.basic') + ); + + const testConfig = { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Prebuilt Rules (ML Disabled) - Integration Tests - ESS Env Basic License', + }, + }; + + return testConfig; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/serverless_essentials_tier.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/serverless_essentials_tier.config.ts new file mode 100644 index 0000000000000..5f28201a6523e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/configs/serverless_essentials_tier.config.ts @@ -0,0 +1,16 @@ +/* + * 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 { createTestConfig } from '../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Prebuilt Rules (ML Disabled) - Integration Tests - Serverless Env Essentials Tier', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/index.ts new file mode 100644 index 0000000000000..3c3f4580947c7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/index.ts @@ -0,0 +1,18 @@ +/* + * 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 { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Rules Management - Prebuilt Rules (ML Disabled)', function () { + loadTestFile(require.resolve('./review_installation')); + loadTestFile(require.resolve('./perform_installation')); + loadTestFile(require.resolve('./review_upgrade')); + loadTestFile(require.resolve('./perform_upgrade')); + loadTestFile(require.resolve('./status')); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/index.ts new file mode 100644 index 0000000000000..92cbf2e78b6a8 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + loadTestFile(require.resolve('./perform_installation')); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts new file mode 100644 index 0000000000000..1c71323c2521d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts @@ -0,0 +1,77 @@ +/* + * 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 expect from 'expect'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + deleteAllPrebuiltRuleAssets, + installPrebuiltRules, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI Prebuilt rules installation perform', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('ML rules are skipped during installation if mode is ALL_RULES', async () => { + const mlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning'); + const nonMlRuleAsset = createRuleAssetSavedObjectOfType('query'); + await createPrebuiltRuleAssetSavedObjects(es, [mlRuleAsset, nonMlRuleAsset]); + + const performInstallationResponse = await installPrebuiltRules(es, supertest); + expect(performInstallationResponse).toMatchObject({ + summary: { + total: 1, + succeeded: 1, + skipped: 0, // ML rules are skipped silently as if they were not present, so they don't count towards "skipped" + failed: 0, + }, + results: { + created: [{ type: 'query' }], + }, + }); + }); + + it('ML rules produce an error during installation if mode is SPECIFIC_RULES', async () => { + const mlRuleFields = { rule_id: 'ml-rule', version: 1 }; + const mlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', mlRuleFields); + + const nonMlRuleFields = { rule_id: 'non-ml-rule', version: 1 }; + const nonMlRuleAsset = createRuleAssetSavedObjectOfType('query', nonMlRuleFields); + + await createPrebuiltRuleAssetSavedObjects(es, [mlRuleAsset, nonMlRuleAsset]); + + const performInstallationResponse = await installPrebuiltRules(es, supertest, [ + mlRuleFields, + nonMlRuleFields, + ]); + + expect(performInstallationResponse).toMatchObject({ + summary: { total: 2, succeeded: 1, skipped: 0, failed: 1 }, + results: { + created: [{ rule_id: nonMlRuleFields.rule_id }], + }, + errors: [ + { + message: 'Your license does not support machine learning. Please upgrade your license.', + status_code: 403, + rules: [{ rule_id: mlRuleFields.rule_id }], + }, + ], + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/index.ts new file mode 100644 index 0000000000000..ce7681a102e60 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + loadTestFile(require.resolve('./perform_upgrade')); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/perform_upgrade.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/perform_upgrade.ts new file mode 100644 index 0000000000000..07e4c6f5a4590 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_upgrade/perform_upgrade.ts @@ -0,0 +1,186 @@ +/* + * 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 expect from 'expect'; +import { ModeEnum } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + deleteAllPrebuiltRuleAssets, + performUpgradePrebuiltRules, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { setUpRuleUpgrade } from '../../../../utils/rules/prebuilt_rules/set_up_rule_upgrade'; +import { createMlRuleThroughAlertingEndpoint } from '../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + const deps = { + es, + supertest, + log, + }; + + describe('@ess @serverless @skipInServerlessMKI Prebuilt rules upgrade perform', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const ruleId = 'ml-rule'; + + describe('ALL_RULES mode', () => { + it('silently skips ML rules in ALL_RULES mode', async () => { + await setUpRuleUpgrade({ + assets: { + installed: { + type: 'machine_learning', + version: 1, + }, + upgrade: { + type: 'machine_learning', + version: 2, + }, + patch: {}, + }, + deps, + }); + + const upgradePerformResult = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + }); + + expect(upgradePerformResult).toMatchObject({ + summary: { + total: 0, + succeeded: 0, + failed: 0, + }, + errors: [], + }); + }); + }); + + describe('SPECIFIC_RULES mode', () => { + it(`doesn't upgrade if target is an ML rule`, async () => { + await createMlRuleThroughAlertingEndpoint(supertest, { + ruleId, + version: 1, + }); + + const targetMlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + rule_id: ruleId, + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetMlRuleAsset]); + + const upgradePerformResult = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + rules: [ + { + rule_id: ruleId, + revision: 0, + version: 2, + }, + ], + }); + + expect(upgradePerformResult).toMatchObject({ + summary: { + total: 1, + failed: 1, + }, + errors: [ + { + message: + 'Your license does not support machine learning. Please upgrade your license.', + rules: [{ rule_id: ruleId }], + }, + ], + }); + }); + + it('upgrades successfully if current rule is an ML rule, but target is a non-ML rule', async () => { + await createMlRuleThroughAlertingEndpoint(supertest, { + ruleId, + version: 1, + }); + + const targetMlRuleAsset = createRuleAssetSavedObjectOfType('query', { + rule_id: ruleId, + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetMlRuleAsset]); + + const upgradePerformResult = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + rules: [ + { + rule_id: ruleId, + revision: 0, + version: 2, + pick_version: 'TARGET', + }, + ], + }); + + expect(upgradePerformResult).toMatchObject({ + summary: { + total: 1, + succeeded: 1, + }, + results: { + updated: [{ rule_id: ruleId }], + }, + }); + }); + + it('upgrades successfully if both current and target are non-ML rules', async () => { + await setUpRuleUpgrade({ + assets: { + installed: { + type: 'query', + version: 1, + rule_id: ruleId, + }, + upgrade: { + type: 'query', + version: 2, + rule_id: ruleId, + }, + patch: {}, + }, + deps, + }); + + const upgradePerformResult = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + rules: [ + { + rule_id: ruleId, + revision: 0, + version: 2, + }, + ], + }); + + expect(upgradePerformResult).toMatchObject({ + summary: { + total: 1, + succeeded: 1, + }, + results: { + updated: [{ rule_id: ruleId }], + }, + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/index.ts new file mode 100644 index 0000000000000..f16796e2162eb --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + loadTestFile(require.resolve('./review_installation')); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/review_installation.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/review_installation.ts new file mode 100644 index 0000000000000..ea5cc275ea94c --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_installation/review_installation.ts @@ -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 expect from 'expect'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + deleteAllPrebuiltRuleAssets, + reviewPrebuiltRulesToInstall, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI Prebuilt rules installation review', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('ML rules are excluded from response', async () => { + const mlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + tags: ['Type: ML'], + }); + const nonMlRuleAsset = createRuleAssetSavedObjectOfType('query', { + tags: ['Type: Custom Query'], + }); + + await createPrebuiltRuleAssetSavedObjects(es, [mlRuleAsset, nonMlRuleAsset]); + + const prebuiltRulesToInstallReview = await reviewPrebuiltRulesToInstall(supertest); + + expect(prebuiltRulesToInstallReview.stats.num_rules_to_install).toBe(1); + expect(prebuiltRulesToInstallReview.rules.length).toBe(1); + expect(prebuiltRulesToInstallReview.rules[0]?.type).toBe('query'); + + // Ensure tags from ML rules are not included + expect(prebuiltRulesToInstallReview.stats.tags).toEqual(['Type: Custom Query']); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/index.ts new file mode 100644 index 0000000000000..b7722e9f25463 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + loadTestFile(require.resolve('./review_upgrade')); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/review_upgrade.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/review_upgrade.ts new file mode 100644 index 0000000000000..69d313483f253 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/review_upgrade/review_upgrade.ts @@ -0,0 +1,128 @@ +/* + * 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 expect from 'expect'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + deleteAllPrebuiltRuleAssets, + reviewPrebuiltRulesToUpgrade, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { createMlRuleThroughAlertingEndpoint } from '../utils'; +import { setUpRuleUpgrade } from '../../../../utils/rules/prebuilt_rules/set_up_rule_upgrade'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + const deps = { + es, + supertest, + log, + }; + + describe('@ess @serverless @skipInServerlessMKI Prebuilt rules upgrade review', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const ruleId = 'ml-rule'; + + it('rule is excluded from response if target is an ML rule', async () => { + await createMlRuleThroughAlertingEndpoint(supertest, { + ruleId, + version: 1, + }); + + const targetMlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + rule_id: ruleId, + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetMlRuleAsset]); + + const upgradeReviewResult = await reviewPrebuiltRulesToUpgrade(supertest); + expect(upgradeReviewResult).toMatchObject({ + total: 0, + rules: [], + }); + }); + + it('rule is included in response if current rule is an ML rule, but target is a non-ML rule', async () => { + await createMlRuleThroughAlertingEndpoint(supertest, { + ruleId, + version: 1, + }); + + const targetRuleAsset = createRuleAssetSavedObjectOfType('query', { + rule_id: ruleId, + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetRuleAsset]); + + const upgradeReviewResult = await reviewPrebuiltRulesToUpgrade(supertest); + expect(upgradeReviewResult).toMatchObject({ + total: 1, + rules: [ + { + current_rule: { + rule_id: ruleId, + type: 'machine_learning', + version: 1, + }, + target_rule: { + rule_id: ruleId, + type: 'query', + version: 2, + }, + }, + ], + }); + }); + + it('rule is included in response if both current and target are non-ML rules', async () => { + await setUpRuleUpgrade({ + assets: { + installed: { + type: 'query', + rule_id: ruleId, + version: 1, + }, + upgrade: { + type: 'query', + rule_id: ruleId, + version: 2, + }, + patch: {}, + }, + deps, + }); + + const upgradeReviewResult = await reviewPrebuiltRulesToUpgrade(supertest); + + expect(upgradeReviewResult).toMatchObject({ + total: 1, + rules: [ + { + current_rule: { + rule_id: ruleId, + type: 'query', + version: 1, + }, + target_rule: { + rule_id: ruleId, + type: 'query', + version: 2, + }, + }, + ], + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/index.ts new file mode 100644 index 0000000000000..75f8b0fa5eed1 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + loadTestFile(require.resolve('./status')); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts new file mode 100644 index 0000000000000..638315dc85bdd --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + deleteAllPrebuiltRuleAssets, + getPrebuiltRulesStatus, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { createMlRuleThroughAlertingEndpoint } from '../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI Prebuilt rules status', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('ML rules are not counted towards installable rules', async () => { + const mlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + version: 1, + }); + await createPrebuiltRuleAssetSavedObjects(es, [mlRuleAsset]); + + const statusResponse = await getPrebuiltRulesStatus(es, supertest); + expect(statusResponse).toMatchObject({ + stats: { + num_prebuilt_rules_to_install: 0, + num_prebuilt_rules_total_in_package: 1, + }, + }); + }); + + it('ML rules are not counted towards upgradable rules', async () => { + await createMlRuleThroughAlertingEndpoint(supertest, { + ruleId: 'ml-rule', + version: 1, + }); + + const targetMlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + rule_id: 'ml-rule', + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetMlRuleAsset]); + + const statusResponse = await getPrebuiltRulesStatus(es, supertest); + expect(statusResponse).toMatchObject({ + stats: { + num_prebuilt_rules_to_upgrade: 0, + num_prebuilt_rules_installed: 1, + num_prebuilt_rules_total_in_package: 1, + }, + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/utils.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/utils.ts new file mode 100644 index 0000000000000..c2f86183a0d1f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/utils.ts @@ -0,0 +1,62 @@ +/* + * 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 SuperTest from 'supertest'; +import { ML_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; +import { RuleParamsWithDefaultValue } from '@kbn/response-ops-rule-params'; +import { CreateRuleRequestBody } from '@kbn/alerting-plugin/common/routes/rule/apis/create'; +import { createRuleThroughAlertingEndpoint } from '../../../utils'; + +/** + * Creates a machine learning rule through the alerting endpoint. + * Useful during testing under Basic and Essential licenses, as the DE rule creation API does not permit the creation of ML rules under those licenses. + */ +export async function createMlRuleThroughAlertingEndpoint( + supertest: SuperTest.Agent, + paramsOverride: Partial = {} +): Promise { + const params: RuleParamsWithDefaultValue = { + alertSuppression: undefined, + anomalyThreshold: 50, + author: [], + description: 'rule', + exceptionsList: [], + falsePositives: [], + from: 'now-5m', + immutable: true, + machineLearningJobId: ['ml-rule-job'], + outputIndex: '', + references: [], + responseActions: undefined, + riskScore: 1, + riskScoreMapping: [], + ruleId: 'ml-rule', + severity: 'low', + severityMapping: [], + threat: [], + to: 'now', + type: 'machine_learning', + version: 1, + ...paramsOverride, + }; + + const body: CreateRuleRequestBody = { + actions: [], + consumer: 'siem', + enabled: false, + name: 'Test ML rule', + params, + rule_type_id: ML_RULE_TYPE_ID, + schedule: { + interval: '5m', + }, + tags: [], + throttle: null, + }; + + await createRuleThroughAlertingEndpoint(supertest, body); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_saved_object.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_saved_object.ts index f4e3f22a0c9a6..39cabfa706fa6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_saved_object.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_rule_saved_object.ts @@ -8,10 +8,9 @@ import type SuperTest from 'supertest'; import { Rule } from '@kbn/alerting-plugin/common'; -import { - BaseRuleParams, - InternalRuleCreate, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { RuleParamsWithDefaultValue } from '@kbn/response-ops-rule-params'; +import { CreateRuleRequestBody } from '@kbn/alerting-plugin/common/routes/rule/apis/create'; /** * Creates a rule using the alerting APIs directly. @@ -22,7 +21,7 @@ import { */ export const createRuleThroughAlertingEndpoint = async ( supertest: SuperTest.Agent, - rule: InternalRuleCreate + rule: CreateRuleRequestBody ): Promise> => { const { body } = await supertest .post('/api/alerting/rule') diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts index 9b315dbd9dbb5..b78df95b4860c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts @@ -5,128 +5,85 @@ * 2.0. */ -import { InternalRuleCreate } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { RuleParamsWithDefaultValue } from '@kbn/response-ops-rule-params'; +import { CreateRuleRequestBody } from '@kbn/alerting-plugin/common/routes/rule/apis/create'; + +const baseBody = { + name: 'Test investigation fields', + tags: ['migration'], + rule_type_id: 'siem.queryRule', + consumer: 'siem', + schedule: { + interval: '5m', + }, + enabled: false, + actions: [], + throttle: null, +}; + +const baseParams: RuleParamsWithDefaultValue = { + alertSuppression: undefined, + author: [], + buildingBlockType: undefined, + dataViewId: undefined, + description: 'a', + exceptionsList: [], + falsePositives: [], + filters: [], + from: '1900-01-01T00:00:00.000Z', + immutable: false, + index: ['auditbeat-*'], + investigationFields: ['client.address', 'agent.name'], + language: 'kuery', + license: '', + maxSignals: 100, + meta: undefined, + namespace: undefined, + note: undefined, + outputIndex: '', + query: '_id:BhbXBmkBR346wHgn4PeZ or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi', + references: [], + relatedIntegrations: [], + requiredFields: undefined, + responseActions: undefined, + riskScore: 21, + riskScoreMapping: [], + ruleId: '2297be91-894c-4831-830f-b424a0ec84f0', + ruleNameOverride: undefined, + savedId: undefined, + setup: '', + severity: 'low', + severityMapping: [], + threat: [], + timelineId: undefined, + timelineTitle: undefined, + timestampOverride: undefined, + timestampOverrideFallbackDisabled: undefined, + to: 'now', + type: 'query', + version: 1, +}; export const getRuleSavedObjectWithLegacyInvestigationFields = ( - rewrites?: Partial -): InternalRuleCreate => - ({ - name: 'Test investigation fields', - tags: ['migration'], - rule_type_id: 'siem.queryRule', - consumer: 'siem', - params: { - author: [], - buildingBlockType: undefined, - falsePositives: [], - description: 'a', - ruleId: '2297be91-894c-4831-830f-b424a0ec84f0', - from: '1900-01-01T00:00:00.000Z', - immutable: false, - license: '', - outputIndex: '', - investigationFields: ['client.address', 'agent.name'], - maxSignals: 100, - meta: undefined, - riskScore: 21, - riskScoreMapping: [], - severity: 'low', - severityMapping: [], - threat: [], - to: 'now', - references: [], - timelineId: undefined, - timelineTitle: undefined, - ruleNameOverride: undefined, - timestampOverride: undefined, - timestampOverrideFallbackDisabled: undefined, - namespace: undefined, - note: undefined, - requiredFields: undefined, - version: 1, - exceptionsList: [], - relatedIntegrations: [], - setup: '', - type: 'query', - language: 'kuery', - index: ['auditbeat-*'], - query: '_id:BhbXBmkBR346wHgn4PeZ or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi', - filters: [], - savedId: undefined, - responseActions: undefined, - alertSuppression: undefined, - dataViewId: undefined, - ...rewrites?.params, - }, - schedule: { - interval: '5m', - }, - enabled: false, - actions: [], - throttle: null, - ...rewrites, - // cast is due to alerting API expecting rule_type_id - // and our internal schema expecting alertTypeId - } as unknown as InternalRuleCreate); + rewrites?: Partial> +): CreateRuleRequestBody => ({ + ...baseBody, + params: { + ...baseParams, + ...rewrites?.params, + }, + ...rewrites, +}); export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = ( - rewrites?: Partial -): InternalRuleCreate => - ({ - name: 'Test investigation fields empty array', - tags: ['migration'], - rule_type_id: 'siem.queryRule', - consumer: 'siem', - params: { - author: [], - description: 'a', - ruleId: '2297be91-894c-4831-830f-b424a0ec5678', - falsePositives: [], - from: '1900-01-01T00:00:00.000Z', - immutable: false, - license: '', - outputIndex: '', - investigationFields: [], - maxSignals: 100, - riskScore: 21, - riskScoreMapping: [], - severity: 'low', - severityMapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptionsList: [], - type: 'query', - language: 'kuery', - index: ['auditbeat-*'], - query: '_id:BhbXBmkBR346wHgn4PeZ or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi', - filters: [], - relatedIntegrations: [], - setup: '', - buildingBlockType: undefined, - meta: undefined, - timelineId: undefined, - timelineTitle: undefined, - ruleNameOverride: undefined, - timestampOverride: undefined, - timestampOverrideFallbackDisabled: undefined, - namespace: undefined, - note: undefined, - requiredFields: undefined, - savedId: undefined, - responseActions: undefined, - alertSuppression: undefined, - dataViewId: undefined, - ...rewrites?.params, - }, - schedule: { - interval: '5m', - }, - enabled: false, - actions: [], - throttle: null, - ...rewrites, - // cast is due to alerting API expecting rule_type_id - // and our internal schema expecting alertTypeId - } as unknown as InternalRuleCreate); + rewrites?: Partial> +): CreateRuleRequestBody => ({ + ...baseBody, + params: { + ...baseParams, + ruleId: 'rule-with-legacy-investigation-fields-empty-array', + investigationFields: [], + ...rewrites?.params, + }, + ...rewrites, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts index 487d2dbe53044..228ca6ff73ed5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts @@ -10,7 +10,7 @@ import { ReviewRuleInstallationResponseBody } from '@kbn/security-solution-plugi import type SuperTest from 'supertest'; /** - * Returns prebuilt rules that are avaialble to install + * Returns prebuilt rules that are available to install * * @param supertest SuperTest instance * @returns Review Install prebuilt rules response diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index f216c39ed94f0..4a5abd5611721 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -2,7 +2,11 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "types": ["node", "jest","@kbn/ambient-ftr-types"] + "types": [ + "node", + "jest", + "@kbn/ambient-ftr-types" + ] }, "include": [ "**/*", @@ -13,11 +17,19 @@ "target/**/*" ], "kbn_references": [ - { "path": "../security_solution_endpoint/tsconfig.json" }, + { + "path": "../security_solution_endpoint/tsconfig.json" + }, "@kbn/test-suites-serverless", - { "path": "../../test_serverless/api_integration/**/*" }, - { "path": "../../test_serverless/shared/**/*" }, - { "path": "../../api_integration/services/**/*" }, + { + "path": "../../test_serverless/api_integration/**/*" + }, + { + "path": "../../test_serverless/shared/**/*" + }, + { + "path": "../../api_integration/services/**/*" + }, "@kbn/dev-utils", "@kbn/test", "@kbn/expect", @@ -57,6 +69,6 @@ "@kbn/security-plugin-types-common", "@kbn/scout-info", "@kbn/babel-register", - "@kbn/config-schema" + "@kbn/response-ops-rule-params" ] -} +} \ No newline at end of file diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts index a8902a3513392..63012448f1aa7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/install_update_error_handling.cy.ts @@ -37,6 +37,10 @@ import { assertRulesPresentInAddPrebuiltRulesTable, assertRuleUpgradeFailureToastShown, assertRulesPresentInRuleUpdatesTable, + interceptInstallationRequestToFailPartially, + assertRuleInstallationSuccessToastShown, + assertRuleUpgradeSuccessToastShown, + interceptUpgradeRequestToFailPartially, } from '../../../../tasks/prebuilt_rules'; import { visitRulesManagementTable } from '../../../../tasks/rules_management'; @@ -113,6 +117,19 @@ describe( assertRuleInstallationFailureToastShown([RULE_1, RULE_2]); assertRulesPresentInAddPrebuiltRulesTable([RULE_1, RULE_2]); }); + + it('installing all available rules at once with some rules succeeding', () => { + clickAddElasticRulesButton(); + interceptInstallationRequestToFailPartially({ + rulesToSucceed: [RULE_1], + rulesToFail: [RULE_2], + }); + cy.get(INSTALL_ALL_RULES_BUTTON).click(); + assertInstallationRequestIsComplete([RULE_1, RULE_2]); + assertRuleInstallationSuccessToastShown([RULE_1]); + assertRuleInstallationFailureToastShown([RULE_2]); + assertRulesPresentInAddPrebuiltRulesTable([RULE_1, RULE_2]); + }); }); describe('Update of prebuilt rules - Should fail gracefully with toast error message when', () => { @@ -203,6 +220,21 @@ describe( assertRuleUpgradeFailureToastShown([OUTDATED_RULE_1, OUTDATED_RULE_2]); assertRulesPresentInRuleUpdatesTable([OUTDATED_RULE_1, OUTDATED_RULE_2]); }); + + it('upgrading all rules with available upgrades at once with some rules succeeding', () => { + interceptUpgradeRequestToFailPartially({ + rulesToSucceed: [OUTDATED_RULE_1], + rulesToFail: [OUTDATED_RULE_2], + }); + + // Navigate to Rule Upgrade table + clickRuleUpdatesTab(); + + cy.get(UPGRADE_ALL_RULES_BUTTON).click(); + assertRuleUpgradeSuccessToastShown([OUTDATED_RULE_1]); + assertRuleUpgradeFailureToastShown([OUTDATED_RULE_2]); + assertRulesPresentInRuleUpdatesTable([OUTDATED_RULE_1, OUTDATED_RULE_2]); + }); }); } ); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts index 175e1694bcabe..510be189788b2 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts @@ -64,10 +64,55 @@ export const interceptInstallationRequestToFail = (rules: Array ({ + rule_id: rule['security-rule'].rule_id, + name: rule['security-rule'].name, + })), + }, + ], + }, + delay: 500, // Add delay to give Cypress time to find the loading spinner + }).as('installPrebuiltRules'); +}; + +export const interceptInstallationRequestToFailPartially = ({ + rulesToSucceed, + rulesToFail, +}: { + rulesToSucceed: Array; + rulesToFail: Array; +}) => { + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/installation/_perform', { + body: { + summary: { + total: rulesToSucceed.length, + succeeded: rulesToSucceed.length, + skipped: 0, + failed: rulesToFail.length, + }, + results: { + created: rulesToSucceed.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + name: rule['security-rule'].name, + })), + skipped: [], + }, + errors: [ + { + message: 'Something went wrong during installation 🤷‍♀️', + rules: rulesToFail.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + name: rule['security-rule'].name, + })), + }, + ], }, delay: 500, // Add delay to give Cypress time to find the loading spinner }).as('installPrebuiltRules'); @@ -77,19 +122,59 @@ export const interceptUpgradeRequestToFail = (rules: Array ({ + rule_id: rule['security-rule'].rule_id, + name: rule['security-rule'].name, + })), + }, + ], + }, + delay: 500, // Add delay to give Cypress time to find the loading spinner + }).as('updatePrebuiltRules'); +}; + +export const interceptUpgradeRequestToFailPartially = ({ + rulesToSucceed, + rulesToFail, +}: { + rulesToSucceed: Array; + rulesToFail: Array; +}) => { + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform', { + body: { + summary: { + total: rulesToSucceed.length + rulesToFail.length, + succeeded: rulesToSucceed.length, + skipped: 0, + failed: rulesToFail.length, + }, + results: { + updated: rulesToSucceed.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + name: rule['security-rule'].name, + })), + skipped: [], }, + errors: [ + { + message: 'Something went wrong during upgrade 🤷‍♀️', + rules: rulesToFail.map((rule) => ({ + rule_id: rule['security-rule'].rule_id, + name: rule['security-rule'].name, + })), + }, + ], }, delay: 500, // Add delay to give Cypress time to find the loading spinner }).as('updatePrebuiltRules'); @@ -101,7 +186,7 @@ export const assertRuleInstallationSuccessToastShown = ( const rulesString = rules.length > 1 ? 'rules' : 'rule'; cy.get(SUCCESS_TOASTER) .should('be.visible') - .should('have.text', `${rules.length} ${rulesString} installed successfully.`); + .should('have.text', `${rules.length} ${rulesString} installed successfully`); }; export const assertRuleInstallationFailureToastShown = ( @@ -110,21 +195,21 @@ export const assertRuleInstallationFailureToastShown = ( const rulesString = rules.length > 1 ? 'rules' : 'rule'; cy.get(TOASTER) .should('be.visible') - .should('have.text', `${rules.length} ${rulesString} failed to install.`); + .should('have.text', `${rules.length} ${rulesString} failed to install`); }; export const assertRuleUpgradeSuccessToastShown = (rules: Array) => { const rulesString = rules.length > 1 ? 'rules' : 'rule'; cy.get(SUCCESS_TOASTER) .should('be.visible') - .should('contain', `${rules.length} ${rulesString} updated successfully.`); + .should('contain', `${rules.length} ${rulesString} updated successfully`); }; export const assertRuleUpgradeFailureToastShown = (rules: Array) => { const rulesString = rules.length > 1 ? 'rules' : 'rule'; cy.get(TOASTER) .should('be.visible') - .should('have.text', `${rules.length} ${rulesString} failed to update.`); + .should('have.text', `${rules.length} ${rulesString} failed to update`); }; export const assertRulesPresentInInstalledRulesTable = ( From 2d491866b8266719940cba13561e8ba2b6aa1f7b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:39:44 +0000 Subject: [PATCH 2/3] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/test/security_solution_api_integration/tsconfig.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index 4a5abd5611721..da10db357c486 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -69,6 +69,7 @@ "@kbn/security-plugin-types-common", "@kbn/scout-info", "@kbn/babel-register", - "@kbn/response-ops-rule-params" + "@kbn/response-ops-rule-params", + "@kbn/config-schema" ] -} \ No newline at end of file +} From 0c5c83d0d4d43be9438d045f4240e8be16dbc9a9 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Thu, 3 Jul 2025 17:12:56 +0200 Subject: [PATCH 3/3] Update test --- .../utils/rules/get_rule_with_legacy_investigation_fields.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts index b78df95b4860c..723da5e5aafd9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts @@ -79,6 +79,7 @@ export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = ( rewrites?: Partial> ): CreateRuleRequestBody => ({ ...baseBody, + name: 'Test investigation fields empty array', params: { ...baseParams, ruleId: 'rule-with-legacy-investigation-fields-empty-array',