From 6242bb9e6dfd47445c20704cb86c8cb7b1176b53 Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Wed, 23 Apr 2025 15:52:20 +0200 Subject: [PATCH] [AI4SOC] Added AI4SOC promotion rules bootstrapping (#217517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Resolves: https://github.com/elastic/security-team/issues/12069** ## Summary Added logic to bootstrap (install, upgrade, and enable) prebuilt promotion rules required for AI4SOC to function. Summary of changes: - Bootstrapping logic now depends on the `externalDetections` (enabled for AI4SOC) and `detections` (enabled for all tiers except AI4SOC) product keys. - For `detections`, we install the prebuilt rules package and the endpoint package as usual, with no additional steps. - For `externalDetections`, we install the rules package and skip the endpoint package installation, but also install on behalf of the user all promotion rules for enabled integrations. - The rule bootstrapping algorithm works as follows: We check if any of the following integrations are installed: Splunk, Microsoft Sentinel, Google SecOps, SentinelOne, or CrowdStrike. For enabled integrations, we identify prebuilt rules associated with them (using the `related_integrations` field) that also have the `Promotion` tag. This defines the set of promotion rules to install. - For each rule in that set, we check if it needs to be installed or upgraded. If the rule is missing, it's installed and enabled by default. If it was previously installed and disabled, it remains disabled. Rules are enabled by default only during initial installation. - Any prebuilt rules that are installed but not considered promotion rules for enabled integrations are deleted from Kibana. - To create promotion rules, users must have at least the Alerting framework CRUD capability. If this capability is missing, an error is thrown. These errors are exposed in the UI to indicate that rule installation didn’t complete successfully. image - The bootstrap endpoint API contract has been extended to support the above scenarios. The response now includes the number of rules modified during the bootstrap process and any encountered errors: ```json { "packages": [ { "name": "security_detection_engine", "version": "9.0.1", "status": "already_installed" } ], "rules": { "total": 5, "installed": 5, "updated": 0, "deleted": 0, "skipped": 0, "errors": [] } } ``` - Frontend cache invalidation logic has also been updated to refetch any cached rules if the bootstrap endpoint reports modifications. ### How to test Currently, there are no promotion rules in the prebuilt rules package, so there's no straightforward way to test rule bootstrapping without uploading a fake rules package: 1. Start Kibana in serverless mode with the following product configuration: ```yaml xpack.securitySolutionServerless.productTypes: [{ product_line: 'ai_soc', product_tier: 'search_ai_lake' }] ``` 2. Create a rules package archive that contains promotion rules or use this archive for testing https://drive.google.com/file/d/1mbx1gjNbcvknbbbopOOXmaIBVvZpVQHC/view?usp=sharing 3. Upload the rules archive to Kibana using the Fleet API: ``` curl -u elastic_serverless:changeme 'http://localhost:5601/api/fleet/epm/packages' \ -H 'content-type: application/zip' \ -H 'x-elastic-internal-origin: Kibana' \ -H 'kbn-xsrf: foo' \ --data-binary @security_detection_engine-9.10.3.zip ``` 3. Enable any of the available integrations, e.g. Crowdstrike 3. Verify that the rules related to the integration were installed (that should happen automatically on navigation on any of Security pages) and enabled. Note that there's no UI for rules yet so it is only possible to read the rules using API (cherry picked from commit 98fa0f8ef4accc7fa501fb5d7f66dab5c9668769) # Conflicts: # x-pack/solutions/security/plugins/security_solution/common/constants.ts # x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_fetch_integrations.ts # x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts # x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts # x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts # x-pack/solutions/security/plugins/security_solution/server/types.ts # x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts --- .../bootstrap_prebuilt_rules.gen.ts | 51 +++++ .../bootstrap_prebuilt_rules.schema.yaml | 53 +++++ .../security_solution/common/constants.ts | 5 + .../common/lib/search_ai_lake/integrations.ts | 15 -- .../configurations/tabs/integrations.tsx | 5 +- .../use_bootstrap_prebuilt_rules.ts | 18 +- .../rule_management/logic/translations.ts | 7 + .../logic/use_upgrade_security_packages.ts | 17 +- .../alert_summary/use_fetch_integrations.ts | 10 +- .../fleet_integration/fleet_integration.ts | 2 +- .../bootstrap_prebuilt_rules.test.ts | 2 + .../bootstrap_prebuilt_rules_handler.ts | 70 ++++-- ...prebuilt_rules_and_timelines_route.test.ts | 12 +- ...tall_prebuilt_rules_and_timelines_route.ts | 84 +------- .../legacy_create_prepackaged_rules.ts | 81 +++++++ .../perform_rule_installation_handler.ts | 2 +- .../detection_engine/prebuilt_rules/index.ts | 1 - .../ensure_latest_rules_package_installed.ts | 8 +- .../find_latest_package_version.ts | 28 +++ .../integrations/install_endpoint_package.ts | 19 ++ ...install_endpoint_security_prebuilt_rule.ts | 0 .../install_prebuilt_rules_package.ts | 33 +++ .../integrations/install_promotion_rules.ts | 147 +++++++++++++ .../prebuilt_rule_assets_client.ts | 8 +- .../prebuilt_rule_objects_client.ts | 14 +- .../routes/__mocks__/request_context.ts | 3 + .../rule_source_importer.ts | 2 +- .../routes/configure_saved_object.test.ts | 2 - .../risk_engine/routes/delete.test.ts | 2 - .../risk_engine/routes/disable.test.ts | 2 - .../risk_engine/routes/enable.test.ts | 2 - .../routes/entity_calculation.test.ts | 2 - .../risk_score/routes/preview.test.ts | 2 - .../indices/delete_indices_route.test.ts | 3 - ...ad_prebuilt_dev_tool_content_route.test.ts | 3 - .../delete_script_route.test.ts | 3 - .../clean_draft_timelines/index.test.ts | 1 - .../get_draft_timelines/index.test.ts | 1 - .../index.test.ts | 2 +- .../timelines/create_timelines/index.test.ts | 1 - .../timelines/get_timeline/index.test.ts | 2 +- .../timelines/get_timelines/index.test.ts | 2 +- .../timelines/import_timelines/index.test.ts | 2 - .../timelines/patch_timelines/index.test.ts | 1 - .../server/request_context_factory.ts | 2 + .../plugins/security_solution/server/types.ts | 2 + .../is_customized_calculation.ts | 200 ++++++++++++++++++ .../cypress/tasks/api_calls/prebuilt_rules.ts | 2 +- 48 files changed, 756 insertions(+), 180 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/lib/search_ai_lake/integrations.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts rename x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/{ => integrations}/ensure_latest_rules_package_installed.ts (76%) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/find_latest_package_version.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_package.ts rename x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/{rules_package => integrations}/install_endpoint_security_prebuilt_rule.ts (100%) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_prebuilt_rules_package.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_promotion_rules.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts index 764f3438d7b54..4b126dcaf1030 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts @@ -32,10 +32,61 @@ export const PackageInstallStatus = z.object({ status: z.enum(['installed', 'already_installed']), }); +export type RuleBootstrapError = z.infer; +export const RuleBootstrapError = z.object({ + /** + * The list of rules that failed to bootstrap + */ + rules: z.array( + z.object({ + /** + * The ID of the rule that failed to bootstrap + */ + rule_id: z.string(), + }) + ), + /** + * The error message + */ + message: z.string(), +}); + +export type RuleBootstrapResults = z.infer; +export const RuleBootstrapResults = z.object({ + /** + * The total number of rules to be processed. This is a dynamic value and depends on the number of integrations installed that have bootstrappable rules + */ + total: z.number().optional(), + /** + * The number of rules that were installed + */ + installed: z.number(), + /** + * The number of rules that were updated + */ + updated: z.number(), + /** + * The number of rules that were deleted + */ + deleted: z.number(), + /** + * The number of rules that were skipped (already installed rules with no updates) + */ + skipped: z.number().optional(), + /** + * The list of bootstrap errors + */ + errors: z.array(RuleBootstrapError), +}); + export type BootstrapPrebuiltRulesResponse = z.infer; export const BootstrapPrebuiltRulesResponse = z.object({ /** * The list of packages that were installed or upgraded */ packages: z.array(PackageInstallStatus), + /** + * The list of rules that were installed or upgraded + */ + rules: RuleBootstrapResults.optional(), }); diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml index 92cb6ccaf2ad1..8cd5fffa8021d 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml @@ -25,6 +25,9 @@ paths: description: The list of packages that were installed or upgraded items: $ref: '#/components/schemas/PackageInstallStatus' + rules: + description: The list of rules that were installed or upgraded + $ref: '#/components/schemas/RuleBootstrapResults' required: - packages @@ -49,3 +52,53 @@ components: - name - version - status + + RuleBootstrapResults: + type: object + properties: + total: + type: number + description: The total number of rules to be processed. This is a dynamic value and depends on the number of integrations installed that have bootstrappable rules + installed: + type: number + description: The number of rules that were installed + updated: + type: number + description: The number of rules that were updated + deleted: + type: number + description: The number of rules that were deleted + skipped: + type: number + description: The number of rules that were skipped (already installed rules with no updates) + errors: + type: array + description: The list of bootstrap errors + items: + $ref: '#/components/schemas/RuleBootstrapError' + required: + - installed + - updated + - deleted + - errors + + RuleBootstrapError: + type: object + properties: + rules: + type: array + description: The list of rules that failed to bootstrap + items: + type: object + properties: + rule_id: + type: string + description: The ID of the rule that failed to bootstrap + required: + - rule_id + message: + type: string + description: The error message + required: + - rules + - message diff --git a/x-pack/solutions/security/plugins/security_solution/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/constants.ts index f48f5d7dfae56..58de49f9620dd 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/constants.ts @@ -538,3 +538,8 @@ export const AI_FOR_SOC_INTEGRATIONS = [ 'sentinel_one', 'crowdstrike', ]; + +/* + * The tag to mark promotion rules that are related to the AI for SOC integrations + */ +export const PROMOTION_RULE_TAG = 'Promotion'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/lib/search_ai_lake/integrations.ts b/x-pack/solutions/security/plugins/security_solution/public/common/lib/search_ai_lake/integrations.ts deleted file mode 100644 index 64dbcd7dceb02..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/common/lib/search_ai_lake/integrations.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** Allow list of integrations to be available in the AI4DSOC integrations page */ -export const SEARCH_AI_LAKE_ALLOWED_INTEGRATIONS: string[] = [ - 'crowdstrike', - 'google_secops', - 'microsoft_sentinel', - 'sentinel_one', - 'splunk', -]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/configurations/tabs/integrations.tsx b/x-pack/solutions/security/plugins/security_solution/public/configurations/tabs/integrations.tsx index 2db09437b01a3..89b3b6a78e71c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/configurations/tabs/integrations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/configurations/tabs/integrations.tsx @@ -10,8 +10,7 @@ import { EuiSkeletonLoading } from '@elastic/eui'; import type { AvailablePackagesHookType } from '@kbn/fleet-plugin/public'; import { Routes, Route } from '@kbn/shared-ux-router'; import { Redirect } from 'react-router-dom'; -import { CONFIGURATIONS_PATH } from '../../../common/constants'; -import { SEARCH_AI_LAKE_ALLOWED_INTEGRATIONS } from '../../common/lib/search_ai_lake/integrations'; +import { AI_FOR_SOC_INTEGRATIONS, CONFIGURATIONS_PATH } from '../../../common/constants'; import { useEnhancedIntegrationCards } from '../../common/lib/search_ai_lake/hooks'; import { ConfigurationTabs, IntegrationsFacets } from '../constants'; import { IntegrationsPage, IntegrationsSkeleton } from './integrations/components'; @@ -28,7 +27,7 @@ export const ConfigurationsIntegrationsHome = React.memo( }); const allowedIntegrations = filteredCards.filter((card) => - SEARCH_AI_LAKE_ALLOWED_INTEGRATIONS.includes(card.name) + AI_FOR_SOC_INTEGRATIONS.includes(card.name) ); const { available, installed } = useEnhancedIntegrationCards(allowedIntegrations); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_bootstrap_prebuilt_rules.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_bootstrap_prebuilt_rules.ts index 379e8c9326fec..82c9159f62c71 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_bootstrap_prebuilt_rules.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/api/hooks/prebuilt_rules/use_bootstrap_prebuilt_rules.ts @@ -13,6 +13,7 @@ import { bootstrapPrebuiltRules } from '../../api'; import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './use_fetch_prebuilt_rules_install_review_query'; import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query'; import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './use_fetch_prebuilt_rules_upgrade_review_query'; +import { useInvalidateFindRulesQuery } from '../use_find_rules_query'; export const BOOTSTRAP_PREBUILT_RULES_KEY = ['POST', BOOTSTRAP_PREBUILT_RULES_URL]; @@ -22,16 +23,19 @@ export const useBootstrapPrebuiltRulesMutation = ( const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery(); const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery(); + const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); return useMutation(() => bootstrapPrebuiltRules(), { ...options, mutationKey: BOOTSTRAP_PREBUILT_RULES_KEY, onSuccess: (...args) => { const response = args[0]; - if ( - response?.packages.find((pkg) => pkg.name === PREBUILT_RULES_PACKAGE_NAME)?.status === - 'installed' - ) { + + // 'installed' means that the package was installed or updated to a newer version + const hasInstalledNewPackageVersion = + response.packages.find((pkg) => pkg.name === PREBUILT_RULES_PACKAGE_NAME)?.status === + 'installed'; + if (hasInstalledNewPackageVersion) { // Invalidate other pre-packaged rules related queries. We need to do // that only in case the prebuilt rules package was installed indicating // that there might be new rules to install. @@ -40,6 +44,12 @@ export const useBootstrapPrebuiltRulesMutation = ( invalidatePrebuiltRulesUpdateReview(); } + const hasRuleUpdates = + response.rules?.deleted || response.rules?.installed || response.rules?.updated; + if (hasRuleUpdates) { + invalidateFindRulesQuery(); + } + if (options?.onSuccess) { options.onSuccess(...args); } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts index 56ebc3e133775..4dc3f3f9614d0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts @@ -55,3 +55,10 @@ export const TIMELINE_PREPACKAGED_SUCCESS = i18n.translate( defaultMessage: 'Installed pre-packaged timeline templates from elastic', } ); + +export const BOOTSTRAP_PREBUILT_RULES_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.bootstrapPrebuiltRulesFailure', + { + defaultMessage: 'Failed to bootstrap prebuilt rules', + } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts index 72246b085c40f..d8a6c2f850836 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts @@ -11,12 +11,27 @@ import { BOOTSTRAP_PREBUILT_RULES_KEY, useBootstrapPrebuiltRulesMutation, } from '../api/hooks/prebuilt_rules/use_bootstrap_prebuilt_rules'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import * as i18n from './translations'; /** * Install or upgrade the security packages (endpoint and prebuilt rules) */ export const useUpgradeSecurityPackages = () => { - const { mutate: bootstrapPrebuiltRules } = useBootstrapPrebuiltRulesMutation(); + const { addError } = useAppToasts(); + + const { mutate: bootstrapPrebuiltRules } = useBootstrapPrebuiltRulesMutation({ + onError: (error) => { + addError(error, { title: i18n.BOOTSTRAP_PREBUILT_RULES_FAILURE }); + }, + onSuccess: ({ rules }) => { + if (rules?.errors.length) { + addError(new Error(rules.errors.map((error) => error.message).join('; ')), { + title: i18n.BOOTSTRAP_PREBUILT_RULES_FAILURE, + }); + } + }, + }); useEffect(() => { bootstrapPrebuiltRules(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_fetch_integrations.ts b/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_fetch_integrations.ts index 604f99913ecc9..10b22916f848a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_fetch_integrations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alert_summary/use_fetch_integrations.ts @@ -8,15 +8,7 @@ import { useMemo } from 'react'; import type { PackageListItem } from '@kbn/fleet-plugin/common'; import { installationStatuses, useGetPackagesQuery } from '@kbn/fleet-plugin/public'; - -// We hardcode these here for now as we currently do not have any other way to filter out all the unwanted integrations. -const AI_FOR_SOC_INTEGRATIONS = [ - 'splunk', // doesnt yet exist - 'google_secops', - 'microsoft_sentinel', - 'sentinel_one', - 'crowdstrike', -]; +import { AI_FOR_SOC_INTEGRATIONS } from '../../../../common/constants'; export interface UseFetchIntegrationsResult { /** diff --git a/x-pack/solutions/security/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/solutions/security/plugins/security_solution/server/fleet_integration/fleet_integration.ts index 88fa30b959791..82f5fb769a1c8 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -47,7 +47,7 @@ import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; import type { LicenseService } from '../../common/license'; import type { ManifestManager } from '../endpoint/services'; import type { IRequestContextFactory } from '../request_context_factory'; -import { installEndpointSecurityPrebuiltRule } from '../lib/detection_engine/prebuilt_rules/logic/rules_package/install_endpoint_security_prebuilt_rule'; +import { installEndpointSecurityPrebuiltRule } from '../lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_security_prebuilt_rule'; import { createPolicyArtifactManifest } from './handlers/create_policy_artifact_manifest'; import { createDefaultPolicy } from './handlers/create_default_policy'; import { validatePolicyAgainstLicense } from './handlers/validate_policy_against_license'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts index c2982d4b16092..3043bf3a3182b 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts @@ -10,6 +10,7 @@ import { bootstrapPrebuiltRulesRoute } from './bootstrap_prebuilt_rules'; import type { Installation, RegistryPackage } from '@kbn/fleet-plugin/common'; import { requestContextMock, serverMock } from '../../../routes/__mocks__'; import { getBootstrapRulesRequest } from '../../../routes/__mocks__/request_responses'; +import { createProductFeaturesServiceMock } from '../../../../product_features_service/mocks'; const packageMock: RegistryPackage = { name: 'detection_engine', @@ -43,6 +44,7 @@ describe('bootstrap_prebuilt_rules_route', () => { jest.clearAllMocks(); server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); + clients.productFeaturesService = createProductFeaturesServiceMock([]); bootstrapPrebuiltRulesRoute(server.router); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts index d3bc06139821f..413f2ec6d5493 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules_handler.ts @@ -6,15 +6,21 @@ */ import type { IKibanaResponse, KibanaRequest, KibanaResponseFactory } from '@kbn/core/server'; +import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { BootstrapPrebuiltRulesResponse } from '../../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; import { installSecurityAiPromptsPackage } from '../../logic/integrations/install_ai_prompts'; +import type { + BootstrapPrebuiltRulesResponse, + PackageInstallStatus, + RuleBootstrapResults, +} from '../../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; import type { SecuritySolutionRequestHandlerContext } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; -import { - installEndpointPackage, - installPrebuiltRulesPackage, -} from '../install_prebuilt_rules_and_timelines/install_prebuilt_rules_package'; +import { installEndpointPackage } from '../../logic/integrations/install_endpoint_package'; +import { installPrebuiltRulesPackage } from '../../logic/integrations/install_prebuilt_rules_package'; +import { installPromotionRules } from '../../logic/integrations/install_promotion_rules'; +import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; +import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; export const bootstrapPrebuiltRulesHandler = async ( context: SecuritySolutionRequestHandlerContext, @@ -24,27 +30,53 @@ export const bootstrapPrebuiltRulesHandler = async ( const siemResponse = buildSiemResponse(response); try { - const ctx = await context.resolve(['securitySolution']); + const ctx = await context.resolve(['securitySolution', 'alerting', 'core']); const securityContext = ctx.securitySolution; const config = securityContext.getConfig(); const securityAIPromptsEnabled = config.experimentalFeatures.securityAIPromptsEnabled; + const savedObjectsClient = ctx.core.savedObjects.client; + const detectionRulesClient = securityContext.getDetectionRulesClient(); + const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient); + const rulesClient = await ctx.alerting.getRulesClient(); + const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + + const productFeatureService = securityContext.getProductFeatureService(); + const isExternalDetectionsEnabled = productFeatureService.isEnabled( + ProductFeatureSecurityKey.externalDetections + ); + + const packageResults: PackageInstallStatus[] = []; + + // Install packages sequentially to avoid high memory usage const prebuiltRulesResult = await installPrebuiltRulesPackage(config, securityContext); - const endpointResult = await installEndpointPackage(config, securityContext); + packageResults.push({ + name: prebuiltRulesResult.package.name, + version: prebuiltRulesResult.package.version, + status: prebuiltRulesResult.status, + }); + + let ruleResults: RuleBootstrapResults | undefined; + if (isExternalDetectionsEnabled) { + ruleResults = await installPromotionRules({ + rulesClient, + detectionRulesClient, + ruleAssetsClient, + ruleObjectsClient, + fleetServices: securityContext.getInternalFleetServices(), + }); + } else { + const endpointResult = await installEndpointPackage(securityContext); + packageResults.push({ + name: endpointResult.package.name, + version: endpointResult.package.version, + status: endpointResult.status, + }); + } const responseBody: BootstrapPrebuiltRulesResponse = { - packages: [ - { - name: prebuiltRulesResult.package.name, - version: prebuiltRulesResult.package.version, - status: prebuiltRulesResult.status, - }, - { - name: endpointResult.package.name, - version: endpointResult.package.version, - status: endpointResult.status, - }, - ], + packages: packageResults, + rules: ruleResults, }; const securityAiPromptsResult = securityAIPromptsEnabled diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.test.ts index 5578aeddef88a..46cea2b6c7d4e 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.test.ts @@ -13,16 +13,16 @@ import { getBasicEmptySearchResponse, } from '../../../routes/__mocks__/request_responses'; import { requestContextMock, serverMock } from '../../../routes/__mocks__'; -import { - installPrebuiltRulesAndTimelinesRoute, - createPrepackagedRules, -} from './install_prebuilt_rules_and_timelines_route'; +import { installPrebuiltRulesAndTimelinesRoute } from './install_prebuilt_rules_and_timelines_route'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import { installPrepackagedTimelines } from '../../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../rule_schema/mocks'; +// eslint-disable-next-line no-restricted-imports +import { legacyCreatePrepackagedRules } from './legacy_create_prepackaged_rules'; + jest.mock('../../logic/rule_assets/prebuilt_rule_assets_client', () => { return { createPrebuiltRuleAssetsClient: () => { @@ -235,7 +235,7 @@ describe('add_prepackaged_rules_route', () => { describe('createPrepackagedRules', () => { test('uses exception lists client from context when available', async () => { - await createPrepackagedRules( + await legacyCreatePrepackagedRules( context.securitySolution, clients.rulesClient, mockExceptionsClient @@ -248,7 +248,7 @@ describe('add_prepackaged_rules_route', () => { test('uses passed in exceptions list client when lists client not available in context', async () => { context.securitySolution.getExceptionListClient.mockImplementation(() => null); - await createPrepackagedRules( + await legacyCreatePrepackagedRules( context.securitySolution, clients.rulesClient, mockExceptionsClient diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts index f24454726c615..3aba40a54be09 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts @@ -5,28 +5,14 @@ * 2.0. */ -import type { RulesClient } from '@kbn/alerting-plugin/server'; -import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { - InstallPrebuiltRulesAndTimelinesResponse, - PREBUILT_RULES_URL, -} from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { - SecuritySolutionApiRequestHandlerContext, - SecuritySolutionPluginRouter, -} from '../../../../../types'; +import { PREBUILT_RULES_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; -import { getExistingPrepackagedRules } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; import { PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS } from '../../constants'; -import { ensureLatestRulesPackageInstalled } from '../../logic/ensure_latest_rules_package_installed'; -import { getRulesToInstall } from '../../logic/get_rules_to_install'; -import { getRulesToUpdate } from '../../logic/get_rules_to_update'; -import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; -import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; -import { createPrebuiltRules } from '../../logic/rule_objects/create_prebuilt_rules'; -import { upgradePrebuiltRules } from '../../logic/rule_objects/upgrade_prebuilt_rules'; -import { rulesToMap } from '../../logic/utils'; + +// eslint-disable-next-line no-restricted-imports +import { legacyCreatePrepackagedRules } from './legacy_create_prepackaged_rules'; export const installPrebuiltRulesAndTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -55,7 +41,7 @@ export const installPrebuiltRulesAndTimelinesRoute = (router: SecuritySolutionPl try { const rulesClient = await (await context.alerting).getRulesClient(); - const validated = await createPrepackagedRules( + const validated = await legacyCreatePrepackagedRules( await context.securitySolution, rulesClient, undefined @@ -71,61 +57,3 @@ export const installPrebuiltRulesAndTimelinesRoute = (router: SecuritySolutionPl } ); }; - -export class PrepackagedRulesError extends Error { - public readonly statusCode: number; - constructor(message: string, statusCode: number) { - super(message); - this.statusCode = statusCode; - } -} - -export const createPrepackagedRules = async ( - context: SecuritySolutionApiRequestHandlerContext, - rulesClient: RulesClient, - exceptionsClient?: ExceptionListClient -): Promise => { - const config = context.getConfig(); - const savedObjectsClient = context.core.savedObjects.client; - const siemClient = context.getAppClient(); - const exceptionsListClient = context.getExceptionListClient() ?? exceptionsClient; - const detectionRulesClient = context.getDetectionRulesClient(); - const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient); - - if (!siemClient || !rulesClient) { - throw new PrepackagedRulesError('', 404); - } - - // This will create the endpoint list if it does not exist yet - if (exceptionsListClient != null) { - await exceptionsListClient.createEndpointList(); - } - - const latestPrebuiltRules = await ensureLatestRulesPackageInstalled( - ruleAssetsClient, - config, - context - ); - - const installedPrebuiltRules = rulesToMap(await getExistingPrepackagedRules({ rulesClient })); - const rulesToInstall = getRulesToInstall(latestPrebuiltRules, installedPrebuiltRules); - const rulesToUpdate = getRulesToUpdate(latestPrebuiltRules, installedPrebuiltRules); - - const result = await createPrebuiltRules(detectionRulesClient, rulesToInstall); - if (result.errors.length > 0) { - throw new AggregateError(result.errors, 'Error installing new prebuilt rules'); - } - - const { result: timelinesResult } = await performTimelinesInstallation(context); - - await upgradePrebuiltRules(detectionRulesClient, rulesToUpdate); - - const prebuiltRulesOutput: InstallPrebuiltRulesAndTimelinesResponse = { - rules_installed: rulesToInstall.length, - rules_updated: rulesToUpdate.length, - timelines_installed: timelinesResult?.timelines_installed ?? 0, - timelines_updated: timelinesResult?.timelines_updated ?? 0, - }; - - return InstallPrebuiltRulesAndTimelinesResponse.parse(prebuiltRulesOutput); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts new file mode 100644 index 0000000000000..5098f5f9e27bd --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts @@ -0,0 +1,81 @@ +/* + * 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 { RulesClient } from '@kbn/alerting-plugin/server'; +import type { ExceptionListClient } from '@kbn/lists-plugin/server'; +import type { InstallPrebuiltRulesAndTimelinesResponse } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; +import { getExistingPrepackagedRules } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; +import { ensureLatestRulesPackageInstalled } from '../../logic/integrations/ensure_latest_rules_package_installed'; +import { getRulesToInstall } from '../../logic/get_rules_to_install'; +import { getRulesToUpdate } from '../../logic/get_rules_to_update'; +import { performTimelinesInstallation } from '../../logic/perform_timelines_installation'; +import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; +import { createPrebuiltRules } from '../../logic/rule_objects/create_prebuilt_rules'; +import { upgradePrebuiltRules } from '../../logic/rule_objects/upgrade_prebuilt_rules'; +import { rulesToMap } from '../../logic/utils'; + +export class PrepackagedRulesError extends Error { + public readonly statusCode: number; + constructor(message: string, statusCode: number) { + super(message); + this.statusCode = statusCode; + } +} + +/** + * @deprecated This method is incompatible with prebuilt rules customization as + * it upgrades prebuilt rules to their 'target' version erasing any + * customizations. Use createPrebuiltRules instead. + */ +export const legacyCreatePrepackagedRules = async ( + context: SecuritySolutionApiRequestHandlerContext, + rulesClient: RulesClient, + exceptionsClient?: ExceptionListClient +): Promise => { + const config = context.getConfig(); + const savedObjectsClient = context.core.savedObjects.client; + const siemClient = context.getAppClient(); + const exceptionsListClient = context.getExceptionListClient() ?? exceptionsClient; + const detectionRulesClient = context.getDetectionRulesClient(); + const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient); + + if (!siemClient || !rulesClient) { + throw new PrepackagedRulesError('', 404); + } + + // This will create the endpoint list if it does not exist yet + if (exceptionsListClient != null) { + await exceptionsListClient.createEndpointList(); + } + + const latestPrebuiltRules = await ensureLatestRulesPackageInstalled( + ruleAssetsClient, + config, + context + ); + + const installedPrebuiltRules = rulesToMap(await getExistingPrepackagedRules({ rulesClient })); + const rulesToInstall = getRulesToInstall(latestPrebuiltRules, installedPrebuiltRules); + const rulesToUpdate = getRulesToUpdate(latestPrebuiltRules, installedPrebuiltRules); + + const result = await createPrebuiltRules(detectionRulesClient, rulesToInstall); + if (result.errors.length > 0) { + throw new AggregateError(result.errors, 'Error installing new prebuilt rules'); + } + + const { result: timelinesResult } = await performTimelinesInstallation(context); + + await upgradePrebuiltRules(detectionRulesClient, rulesToUpdate); + + return { + rules_installed: rulesToInstall.length, + rules_updated: rulesToUpdate.length, + timelines_installed: timelinesResult?.timelines_installed ?? 0, + timelines_updated: timelinesResult?.timelines_updated ?? 0, + }; +}; 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..5c68db65e5c4d 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 @@ -16,7 +16,7 @@ import type { import type { SecuritySolutionRequestHandlerContext } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors'; -import { ensureLatestRulesPackageInstalled } from '../../logic/ensure_latest_rules_package_installed'; +import { ensureLatestRulesPackageInstalled } from '../../logic/integrations/ensure_latest_rules_package_installed'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; import { createPrebuiltRules } from '../../logic/rule_objects/create_prebuilt_rules'; import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/index.ts index 56a0ebd88ff86..b331363143248 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -export { createPrepackagedRules } from './api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route'; export { registerPrebuiltRulesRoutes } from './api/register_routes'; export { prebuiltRuleAssetType } from './logic/rule_assets/prebuilt_rule_assets_type'; export { PrebuiltRuleAsset } from './model/rule_assets/prebuilt_rule_asset'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/ensure_latest_rules_package_installed.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/ensure_latest_rules_package_installed.ts similarity index 76% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/ensure_latest_rules_package_installed.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/ensure_latest_rules_package_installed.ts index 6ab16635374ee..074f9a07f2a4d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/ensure_latest_rules_package_installed.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/ensure_latest_rules_package_installed.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { ConfigType } from '../../../../config'; -import type { SecuritySolutionApiRequestHandlerContext } from '../../../../types'; -import type { IPrebuiltRuleAssetsClient } from './rule_assets/prebuilt_rule_assets_client'; -import { installPrebuiltRulesPackage } from '../api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package'; +import type { ConfigType } from '../../../../../config'; +import type { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; +import type { IPrebuiltRuleAssetsClient } from '../rule_assets/prebuilt_rule_assets_client'; +import { installPrebuiltRulesPackage } from './install_prebuilt_rules_package'; export async function ensureLatestRulesPackageInstalled( ruleAssetsClient: IPrebuiltRuleAssetsClient, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/find_latest_package_version.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/find_latest_package_version.ts new file mode 100644 index 0000000000000..1105503824155 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/find_latest_package_version.ts @@ -0,0 +1,28 @@ +/* + * 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 { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; + +export async function findLatestPackageVersion( + context: SecuritySolutionApiRequestHandlerContext, + packageName: string +) { + const securityAppClient = context.getAppClient(); + const packageClient = context.getInternalFleetServices().packages; + + // Use prerelease versions in dev environment + const isPrerelease = + securityAppClient.getBuildFlavor() === 'traditional' && + (securityAppClient.getKibanaVersion().includes('-SNAPSHOT') || + securityAppClient.getKibanaBranch() === 'main'); + + const result = await packageClient.fetchFindLatestPackage(packageName, { + prerelease: isPrerelease, + }); + + return result.version; +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_package.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_package.ts new file mode 100644 index 0000000000000..74e1e24067c2e --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_package.ts @@ -0,0 +1,19 @@ +/* + * 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 { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; +import { ENDPOINT_PACKAGE_NAME } from '../../../../../../common/detection_engine/constants'; +import { findLatestPackageVersion } from './find_latest_package_version'; + +export async function installEndpointPackage(context: SecuritySolutionApiRequestHandlerContext) { + const pkgVersion = await findLatestPackageVersion(context, ENDPOINT_PACKAGE_NAME); + + return context.getInternalFleetServices().packages.ensureInstalledPackage({ + pkgName: ENDPOINT_PACKAGE_NAME, + pkgVersion, + }); +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rules_package/install_endpoint_security_prebuilt_rule.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_security_prebuilt_rule.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rules_package/install_endpoint_security_prebuilt_rule.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_endpoint_security_prebuilt_rule.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_prebuilt_rules_package.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_prebuilt_rules_package.ts new file mode 100644 index 0000000000000..0fcb20531a34b --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_prebuilt_rules_package.ts @@ -0,0 +1,33 @@ +/* + * 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 { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; +import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../../common/detection_engine/constants'; +import type { ConfigType } from '../../../../../config'; +import { findLatestPackageVersion } from './find_latest_package_version'; + +/** + * Installs the prebuilt rules package of the config's specified or latest version. + * + * @param config Kibana config + * @param context Request handler context + */ +export async function installPrebuiltRulesPackage( + config: ConfigType, + context: SecuritySolutionApiRequestHandlerContext +) { + let pkgVersion = config.prebuiltRulesPackageVersion; + + if (!pkgVersion) { + // Find latest package if the version isn't specified in the config + pkgVersion = await findLatestPackageVersion(context, PREBUILT_RULES_PACKAGE_NAME); + } + + return context + .getInternalFleetServices() + .packages.ensureInstalledPackage({ pkgName: PREBUILT_RULES_PACKAGE_NAME, pkgVersion }); +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_promotion_rules.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_promotion_rules.ts new file mode 100644 index 0000000000000..4984a50a2e57c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/integrations/install_promotion_rules.ts @@ -0,0 +1,147 @@ +/* + * 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 { BulkOperationError, RulesClient } from '@kbn/alerting-plugin/server'; +import type { IDetectionRulesClient } from '../../../rule_management/logic/detection_rules_client/detection_rules_client_interface'; +import type { IPrebuiltRuleAssetsClient } from '../rule_assets/prebuilt_rule_assets_client'; +import { createPrebuiltRules } from '../rule_objects/create_prebuilt_rules'; +import type { IPrebuiltRuleObjectsClient } from '../rule_objects/prebuilt_rule_objects_client'; +import { upgradePrebuiltRules } from '../rule_objects/upgrade_prebuilt_rules'; +import type { + RuleBootstrapError, + RuleBootstrapResults, +} from '../../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; +import { getErrorMessage } from '../../../../../utils/error_helpers'; +import type { EndpointInternalFleetServicesInterface } from '../../../../../endpoint/services/fleet'; +import { AI_FOR_SOC_INTEGRATIONS, PROMOTION_RULE_TAG } from '../../../../../../common/constants'; + +interface InstallPromotionRulesParams { + rulesClient: RulesClient; + detectionRulesClient: IDetectionRulesClient; + ruleAssetsClient: IPrebuiltRuleAssetsClient; + ruleObjectsClient: IPrebuiltRuleObjectsClient; + fleetServices: EndpointInternalFleetServicesInterface; +} + +/** + * Install or upgrade promotion rules. These rules are needed for the AI4SOC + * tier integrations to work (promote alerts from external SIEMs). + * + * Note: due to current limitations, the promotion rules cannot be placed in + * their corresponding packages and are shipped as part of the + * security_detection_engine package. However, the promotion rules should only + * be installed if the corresponding integration package is installed. The logic + * is following: we go through the list of known AI4SOC integrations and check + * if their package are installed. Then for installed integrations, we need to + * find corresponding promotion rules, they are tagged as Promotion, and install + * or upgrade them to latest versions. + * + */ +export async function installPromotionRules({ + rulesClient, + detectionRulesClient, + ruleAssetsClient, + ruleObjectsClient, + fleetServices, +}: InstallPromotionRulesParams): Promise { + // Get the list of installed integrations + const installedIntegrations = new Set( + ( + await Promise.all( + AI_FOR_SOC_INTEGRATIONS.map(async (integration) => { + // We don't care about installation status of the integration as all + // IA4SOC integrations are agentless (don't require setting up an + // integration policy). So the fact that the corresponding package is + // installed is enough. + const installation = await fleetServices.packages.getInstallation(integration); + return installation ? integration : []; + }) + ) + ).flat() + ); + + const latestRuleAssets = await ruleAssetsClient.fetchLatestAssets(); + + const latestPromotionRules = latestRuleAssets.filter((rule) => { + // Rule should be tagged as 'Promotion' and should be related to an enabled integration + return ( + (rule.tags ?? []).includes(PROMOTION_RULE_TAG) && + rule.related_integrations?.some((integration) => + installedIntegrations.has(integration.package) + ) + ); + }); + + const installedRuleVersions = await ruleObjectsClient.fetchInstalledRuleVersions(); + const installedRuleVersionsMap = new Map( + installedRuleVersions.map((version) => [version.rule_id, version]) + ); + + const promotionRulesToInstall = latestPromotionRules.filter(({ rule_id: ruleId }) => { + return !installedRuleVersionsMap.has(ruleId); + }); + const { results: installationResults, errors: installationErrors } = await createPrebuiltRules( + detectionRulesClient, + promotionRulesToInstall.map((asset) => ({ + ...asset, + enabled: true, + })) + ); + + const promotionRulesToUpgrade = latestPromotionRules.filter(({ rule_id: ruleId, version }) => { + const installedVersion = installedRuleVersionsMap.get(ruleId); + return installedVersion && installedVersion.version < version; + }); + const { results: upgradeResults, errors: upgradeErrors } = await upgradePrebuiltRules( + detectionRulesClient, + promotionRulesToUpgrade + ); + + // Cleanup any unknown rules, we don't allow users to install any detection + // rules that are not part of the enabled integrations, although that is not + // enforced via the API + const rulesToDelete = installedRuleVersions.filter(({ rule_id: ruleId }) => { + return !latestPromotionRules.some((rule) => rule.rule_id === ruleId); + }); + const deletionErrors: BulkOperationError[] = []; + if (rulesToDelete.length > 0) { + const bulkDeleteResult = await rulesClient.bulkDeleteRules({ + ids: rulesToDelete.map(({ id }) => id), + }); + deletionErrors.push(...bulkDeleteResult.errors); + } + + const alreadyUpToDate = latestPromotionRules.filter(({ rule_id: ruleId, version }) => { + const installedVersion = installedRuleVersionsMap.get(ruleId); + return installedVersion && installedVersion.version === version; + }); + + const allErrors = [...installationErrors, ...upgradeErrors, ...deletionErrors].reduce( + (errorsMap, currentError) => { + const errorMessage = + 'error' in currentError ? getErrorMessage(currentError.error) : currentError.message; + const ruleId = 'item' in currentError ? currentError.item.rule_id : currentError.rule.id; + const existingError = errorsMap.get(errorMessage); + if (existingError) { + existingError.rules.push({ rule_id: ruleId }); + } else { + errorsMap.set(errorMessage, { rules: [{ rule_id: ruleId }], message: errorMessage }); + } + return errorsMap; + }, + new Map() + ); + + return { + total: latestPromotionRules.length, + installed: installationResults.length, + updated: upgradeResults.length, + deleted: rulesToDelete.length, + skipped: alreadyUpToDate.length, + errors: allErrors.size > 0 ? Array.from(allErrors.values()) : [], + }; +} 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 a6eb5d7367134..84f46b105c689 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 @@ -75,10 +75,14 @@ export const createPrebuiltRuleAssetsClient = ( }); }, - fetchLatestVersions: (ruleIds: string[] = []): Promise => { + fetchLatestVersions: (ruleIds?: string[]): Promise => { return withSecuritySpan('IPrebuiltRuleAssetsClient.fetchLatestVersions', async () => { + if (ruleIds && ruleIds.length === 0) { + return []; + } + const filter = ruleIds - .map((ruleId) => `${PREBUILT_RULE_ASSETS_SO_TYPE}.attributes.rule_id: ${ruleId}`) + ?.map((ruleId) => `${PREBUILT_RULE_ASSETS_SO_TYPE}.attributes.rule_id: ${ruleId}`) .join(' OR '); const findResult = await savedObjectsClient.find< 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..729f8ce2dcc2b 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,6 +7,7 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { + RuleObjectId, RuleResponse, RuleSignatureId, RuleTagArray, @@ -49,15 +50,18 @@ interface FetchInstalledRulesByIdsArgs { sortOrder?: SortOrder; } +type RuleSummary = RuleVersionSpecifier & { + id: RuleObjectId; + 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 +129,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 +156,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/routes/__mocks__/request_context.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index b9d23fc1e6db9..ab8cc7e85ea43 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 @@ -43,6 +43,7 @@ import { detectionRulesClientMock } from '../../rule_management/logic/detection_ import { packageServiceMock } from '@kbn/fleet-plugin/server/services/epm/package_service.mock'; import type { EndpointInternalFleetServicesInterface } from '../../../../endpoint/services/fleet'; import { siemMigrationsServiceMock } from '../../../siem_migrations/__mocks__/mocks'; +import { createProductFeaturesServiceMock } from '../../../product_features_service/mocks'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); @@ -81,6 +82,7 @@ export const createMockClients = () => { }, siemRuleMigrationsClient: siemMigrationsServiceMock.createRulesClient(), getInferenceClient: jest.fn(), + productFeaturesService: createProductFeaturesServiceMock(), }; }; @@ -170,6 +172,7 @@ const createSecuritySolutionRequestContextMock = ( getEntityStoreDataClient: jest.fn(() => clients.entityStoreDataClient), getSiemRuleMigrationsClient: jest.fn(() => clients.siemRuleMigrationsClient), getInferenceClient: jest.fn(() => clients.getInferenceClient()), + getProductFeatureService: jest.fn(() => clients.productFeaturesService), }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/rule_source_importer/rule_source_importer.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/rule_source_importer/rule_source_importer.ts index e6900a5ce2683..a22ca4ff7e7b9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/rule_source_importer/rule_source_importer.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/rule_source_importer/rule_source_importer.ts @@ -21,7 +21,7 @@ import type { } from '../../../../../../../common/api/detection_engine'; import type { PrebuiltRuleAsset } from '../../../../prebuilt_rules'; import type { IPrebuiltRuleAssetsClient } from '../../../../prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; -import { ensureLatestRulesPackageInstalled } from '../../../../prebuilt_rules/logic/ensure_latest_rules_package_installed'; +import { ensureLatestRulesPackageInstalled } from '../../../../prebuilt_rules/logic/integrations/ensure_latest_rules_package_installed'; import { calculateRuleSourceForImport } from '../calculate_rule_source_for_import'; import type { CalculatedRuleSource, IRuleSourceImporter } from './rule_source_importer_interface'; import type { IPrebuiltRuleObjectsClient } from '../../../../prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/configure_saved_object.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/configure_saved_object.test.ts index c70af5c70c7af..813cbb06bca8a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/configure_saved_object.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/configure_saved_object.test.ts @@ -24,8 +24,6 @@ describe('riskEnginConfigureSavedObjectRoute', () => { let getStartServicesMock: jest.Mock; beforeEach(() => { - jest.resetAllMocks(); - server = serverMock.create(); const { clients } = requestContextMock.createTools(); mockRiskEngineDataClient = riskEngineDataClientMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts index 29e43de5d8fb7..691b572615022 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts @@ -24,8 +24,6 @@ describe('risk engine cleanup route', () => { let getStartServicesMock: jest.Mock; beforeEach(() => { - jest.resetAllMocks(); - server = serverMock.create(); const { clients } = requestContextMock.createTools(); mockRiskEngineDataClient = riskEngineDataClientMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.test.ts index 429b13f3a3738..13d39cd514197 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.test.ts @@ -24,8 +24,6 @@ describe('risk score disable route', () => { let getStartServicesMock: jest.Mock; beforeEach(() => { - jest.resetAllMocks(); - server = serverMock.create(); const { clients } = requestContextMock.createTools(); mockRiskEngineDataClient = riskEngineDataClientMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.test.ts index 4932de0b2d270..2c558199af393 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.test.ts @@ -24,8 +24,6 @@ describe('risk score enable route', () => { let getStartServicesMock: jest.Mock; beforeEach(() => { - jest.resetAllMocks(); - server = serverMock.create(); const { clients } = requestContextMock.createTools(); mockRiskEngineDataClient = riskEngineDataClientMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.test.ts index 54b42d02c5495..44b37c804c9e2 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.test.ts @@ -36,8 +36,6 @@ describe('entity risk score calculation route', () => { let getStartServicesMock: jest.Mock; beforeEach(() => { - jest.resetAllMocks(); - getStartServicesMock = jest.fn().mockResolvedValue([ {}, { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts index 6aa2339b999d6..a40849086891f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts @@ -29,8 +29,6 @@ describe('POST risk_engine/preview route', () => { let mockRiskScoreService: ReturnType; beforeEach(() => { - jest.resetAllMocks(); - server = serverMock.create(); logger = loggerMock.create(); ({ clients, context } = requestContextMock.createTools()); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.test.ts index 701fa8286b073..9b03906417879 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.test.ts @@ -27,9 +27,6 @@ describe('deleteEsIndicesRoute', () => { let { context } = requestContextMock.createTools(); beforeEach(() => { - jest.resetModules(); - jest.resetAllMocks(); - server = serverMock.create(); ({ context } = requestContextMock.createTools()); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.test.ts index d1b05736beb49..3d5195cfc5771 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.test.ts @@ -25,9 +25,6 @@ describe('readPrebuiltDevToolContentRoute', () => { let { context } = requestContextMock.createTools(); beforeEach(() => { - jest.resetModules(); - jest.resetAllMocks(); - server = serverMock.create(); ({ context } = requestContextMock.createTools()); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.test.ts index 8d95c0854bcc4..1462aa81bb71d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.test.ts @@ -27,9 +27,6 @@ describe('deleteStoredScriptRoute', () => { let { context } = requestContextMock.createTools(); beforeEach(() => { - jest.resetModules(); - jest.resetAllMocks(); - server = serverMock.create(); ({ context } = requestContextMock.createTools()); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts index ba727f6c0d707..80d6339a95e08 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts @@ -34,7 +34,6 @@ describe('clean draft timelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); jest.clearAllMocks(); server = serverMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts index 75c9b361bc78f..c66c7ee466d1b 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts @@ -33,7 +33,6 @@ describe('get draft timelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); jest.clearAllMocks(); server = serverMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts index 9513bd49b4e6d..65f09c8e7663a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts @@ -40,7 +40,7 @@ describe('installPrepackagedTimelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); + jest.clearAllMocks(); server = serverMock.create(); context = requestContextMock.createTools().context; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts index a510f24a35637..d3e337ef1fddf 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts @@ -44,7 +44,6 @@ describe('create timelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); jest.clearAllMocks(); server = serverMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts index 781da93355442..0e934f140cb11 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts @@ -24,7 +24,7 @@ describe('get timeline', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); + jest.clearAllMocks(); server = serverMock.create(); context = requestContextMock.createTools().context; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts index 753ac1eb15aea..6d071f16d3cce 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts @@ -20,7 +20,7 @@ describe('get all timelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); + jest.clearAllMocks(); server = serverMock.create(); context = requestContextMock.createTools().context; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts index 5528d92d34240..9d473b763c43b 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts @@ -51,7 +51,6 @@ describe('import timelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); jest.clearAllMocks(); server = serverMock.create(); @@ -469,7 +468,6 @@ describe('import timeline templates', () => { const mockNewTemplateTimelineId = 'new templateTimelineId'; beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); jest.clearAllMocks(); server = serverMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts index bb2ba80526d00..684f2b485c939 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts @@ -41,7 +41,6 @@ describe('update timelines', () => { beforeEach(() => { jest.resetModules(); - jest.resetAllMocks(); jest.clearAllMocks(); server = serverMock.create(); 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 40ef090798741..bd2e7abe9dab0 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 @@ -156,6 +156,8 @@ export class RequestContextFactory implements IRequestContextFactory { getEntityStoreApiKeyManager, + getProductFeatureService: () => productFeaturesService, + getDetectionRulesClient: memoize(() => { const mlAuthz = buildMlAuthz({ license: licensing.license, 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 7356e8268ef41..54409e4d1878f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/types.ts @@ -39,6 +39,7 @@ import type { IDetectionRulesClient } from './lib/detection_engine/rule_manageme import type { EntityStoreDataClient } from './lib/entity_analytics/entity_store/entity_store_data_client'; import type { SiemRuleMigrationsClient } from './lib/siem_migrations/rules/siem_rule_migrations_service'; import type { ApiKeyManager } from './lib/entity_analytics/entity_store/auth/api_key'; +import type { ProductFeaturesService } from './lib/product_features_service'; export { AppClient }; export interface SecuritySolutionApiRequestHandlerContext { @@ -65,6 +66,7 @@ export interface SecuritySolutionApiRequestHandlerContext { getEntityStoreDataClient: () => EntityStoreDataClient; getSiemRuleMigrationsClient: () => SiemRuleMigrationsClient; getInferenceClient: () => InferenceClient; + getProductFeatureService: () => ProductFeaturesService; } export type SecuritySolutionRequestHandlerContext = CustomRequestHandlerContext<{ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts new file mode 100644 index 0000000000000..b9f27dc627fbc --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts @@ -0,0 +1,200 @@ +/* + * 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 { + BulkActionEditTypeEnum, + BulkActionTypeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen'; +import expect from 'expect'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + deleteAllPrebuiltRuleAssets, + installPrebuiltRules, +} from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + const log = getService('log'); + + const ruleAsset = createRuleAssetSavedObject({ + rule_id: '000047bb-b27a-47ec-8b62-ef1a5d2c9e19', + tags: ['test-tag'], + }); + + describe('@ess @serverless @skipInServerlessMKI is_customized calculation', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + describe('prebuilt rules', () => { + it('should set is_customized to true on bulk rule modification', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); + await installPrebuiltRules(es, supertest); + + const { body: findResult } = await securitySolutionApi + .findRules({ + query: { + per_page: 1, + filter: `alert.attributes.params.immutable: true`, + }, + }) + .expect(200); + const prebuiltRule = findResult.data[0]; + expect(prebuiltRule).toBeDefined(); + expect(prebuiltRule.rule_source.is_customized).toEqual(false); + + const { body: bulkResult } = await securitySolutionApi + .performRulesBulkAction({ + query: {}, + body: { + ids: [prebuiltRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.add_tags, + value: ['new-tag'], + }, + ], + }, + }) + .expect(200); + + expect(bulkResult.attributes.summary).toEqual({ + failed: 0, + skipped: 0, + succeeded: 1, + total: 1, + }); + expect(bulkResult.attributes.results.updated[0].rule_source.is_customized).toEqual(true); + }); + + it('should leave is_customized intact if the change has been skipped', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); + await installPrebuiltRules(es, supertest); + + const { body: findResult } = await securitySolutionApi + .findRules({ + query: { + per_page: 1, + filter: `alert.attributes.params.immutable: true`, + }, + }) + .expect(200); + const prebuiltRule = findResult.data[0]; + expect(prebuiltRule).toBeDefined(); + expect(prebuiltRule.rule_source.is_customized).toEqual(false); + + const { body: bulkResult } = await securitySolutionApi + .performRulesBulkAction({ + query: {}, + body: { + ids: [prebuiltRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.add_tags, + // This tag is already present on the rule, so the change will be skipped + value: [prebuiltRule.tags[0]], + }, + ], + }, + }) + .expect(200); + + expect(bulkResult.attributes.summary).toEqual({ + failed: 0, + skipped: 1, + succeeded: 0, + total: 1, + }); + + // Check that the rule has not been customized + const { body: findResultAfter } = await securitySolutionApi + .findRules({ + query: { + per_page: 1, + filter: `alert.attributes.params.immutable: true`, + }, + }) + .expect(200); + expect(findResultAfter.data[0].rule_source.is_customized).toEqual(false); + }); + + it('should set is_customized to false if the change has been reverted', async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); + await installPrebuiltRules(es, supertest); + + const { body: findResult } = await securitySolutionApi + .findRules({ + query: { + per_page: 1, + filter: `alert.attributes.params.immutable: true`, + }, + }) + .expect(200); + const prebuiltRule = findResult.data[0]; + expect(prebuiltRule).toBeDefined(); + expect(prebuiltRule.rule_source.is_customized).toEqual(false); + + // Add a tag to the rule + const { body: bulkResult } = await securitySolutionApi + .performRulesBulkAction({ + query: {}, + body: { + ids: [prebuiltRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.add_tags, + value: ['new-tag'], + }, + ], + }, + }) + .expect(200); + + expect(bulkResult.attributes.summary).toEqual({ + failed: 0, + skipped: 0, + succeeded: 1, + total: 1, + }); + + // Remove the added tag + const { body: revertResult } = await securitySolutionApi + .performRulesBulkAction({ + query: {}, + body: { + ids: [prebuiltRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.delete_tags, + value: ['new-tag'], + }, + ], + }, + }) + .expect(200); + + expect(revertResult.attributes.summary).toEqual({ + failed: 0, + skipped: 0, + succeeded: 1, + total: 1, + }); + + expect(revertResult.attributes.results.updated[0].rule_source.is_customized).toEqual(false); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts index 2a0b601b2dc60..2fd936202902c 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts @@ -183,7 +183,7 @@ export const getRuleAssets = (index: string | undefined = '.kibana_security_solu /* during e2e tests, and allow for manual installation of mock rules instead. */ export const preventPrebuiltRulesPackageInstallation = () => { cy.log('Prevent prebuilt rules package installation'); - cy.intercept('POST', BOOTSTRAP_PREBUILT_RULES_URL, {}); + cy.intercept('POST', BOOTSTRAP_PREBUILT_RULES_URL, { packages: [] }); }; /**