[Security Solution] Add timelines installation to the new rule upgrade/install endpoints (#159694)

**Resolves: https://github.com/elastic/kibana/issues/152860**

To replicate the behavior of the legacy prebuilt rule endpoint, this PR
introduces a call to install prebuilt timeline templates each time any
of the following endpoints are invoked:
- `POST /internal/detection_engine/prebuilt_rules/installation/_perform`
- `POST /internal/detection_engine/prebuilt_rules/upgrade/_perform`
This commit is contained in:
Dmitrii Shevchenko 2023-06-15 11:56:53 +02:00 committed by GitHub
parent 25bf774893
commit 16193c6544
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 28 deletions

View file

@ -18,18 +18,16 @@ import type {
SecuritySolutionApiRequestHandlerContext,
SecuritySolutionPluginRouter,
} from '../../../../../types';
import { installPrepackagedTimelines } from '../../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines';
import { buildSiemResponse } from '../../../routes/utils';
import { importTimelineResultSchema } from '../../../../../../common/types/timeline/api';
import { getExistingPrepackagedRules } from '../../../rule_management/logic/search/get_existing_prepackaged_rules';
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';
import { ensureLatestRulesPackageInstalled } from '../../logic/ensure_latest_rules_package_installed';
export const installPrebuiltRulesAndTimelinesRoute = (router: SecuritySolutionPluginRouter) => {
router.put(
@ -84,14 +82,11 @@ export const createPrepackagedRules = async (
exceptionsClient?: ExceptionListClient
): Promise<InstallPrebuiltRulesAndTimelinesResponse | null> => {
const config = context.getConfig();
const frameworkRequest = context.getFrameworkRequest();
const savedObjectsClient = context.core.savedObjects.client;
const siemClient = context.getAppClient();
const exceptionsListClient = context.getExceptionListClient() ?? exceptionsClient;
const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient);
const { maxTimelineImportExportSize } = config;
if (!siemClient || !rulesClient) {
throw new PrepackagedRulesError('', 404);
}
@ -116,14 +111,8 @@ export const createPrepackagedRules = async (
throw new AggregateError(result.errors, 'Error installing new prebuilt rules');
}
const timeline = await installPrepackagedTimelines(
maxTimelineImportExportSize,
frameworkRequest,
true
);
const [prepackagedTimelinesResult, timelinesErrors] = validate(
timeline,
importTimelineResultSchema
const { result: timelinesResult, error: timelinesError } = await performTimelinesInstallation(
context
);
await upgradePrebuiltRules(rulesClient, rulesToUpdate);
@ -131,8 +120,8 @@ export const createPrepackagedRules = async (
const prebuiltRulesOutput: InstallPrebuiltRulesAndTimelinesResponse = {
rules_installed: rulesToInstall.length,
rules_updated: rulesToUpdate.length,
timelines_installed: prepackagedTimelinesResult?.timelines_installed ?? 0,
timelines_updated: prepackagedTimelinesResult?.timelines_updated ?? 0,
timelines_installed: timelinesResult?.timelines_installed ?? 0,
timelines_updated: timelinesResult?.timelines_updated ?? 0,
};
const [validated, genericErrors] = validate(
@ -140,9 +129,9 @@ export const createPrepackagedRules = async (
InstallPrebuiltRulesAndTimelinesResponse
);
if (genericErrors != null && timelinesErrors != null) {
if (genericErrors != null && timelinesError != null) {
throw new PrepackagedRulesError(
[genericErrors, timelinesErrors].filter((msg) => msg != null).join(', '),
[genericErrors, timelinesError].filter((msg) => msg != null).join(', '),
500
);
}

View file

@ -25,6 +25,7 @@ import { createPrebuiltRules } from '../../logic/rule_objects/create_prebuilt_ru
import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client';
import { fetchRuleVersionsTriad } from '../../logic/rule_versions/fetch_rule_versions_triad';
import { getVersionBuckets } from '../../model/rule_versions/get_version_buckets';
import { performTimelinesInstallation } from '../../logic/perform_timelines_installation';
export const performRuleInstallationRoute = (router: SecuritySolutionPluginRouter) => {
router.post(
@ -98,20 +99,32 @@ export const performRuleInstallationRoute = (router: SecuritySolutionPluginRoute
rulesClient,
installableRules
);
const combinedErrors = [...fetchErrors, ...installationErrors];
const ruleErrors = [...fetchErrors, ...installationErrors];
const { error: timelineInstallationError } = await performTimelinesInstallation(
ctx.securitySolution
);
const allErrors = aggregatePrebuiltRuleErrors(ruleErrors);
if (timelineInstallationError) {
allErrors.push({
message: timelineInstallationError,
rules: [],
});
}
const body: PerformRuleInstallationResponseBody = {
summary: {
total: installedRules.length + skippedRules.length + combinedErrors.length,
total: installedRules.length + skippedRules.length + ruleErrors.length,
succeeded: installedRules.length,
skipped: skippedRules.length,
failed: combinedErrors.length,
failed: ruleErrors.length,
},
results: {
created: installedRules.map(({ result }) => internalRuleToAPIResponse(result)),
skipped: skippedRules,
},
errors: aggregatePrebuiltRuleErrors(combinedErrors),
errors: allErrors,
};
return response.ok({ body });

View file

@ -23,6 +23,7 @@ import type { PromisePoolError } from '../../../../../utils/promise_pool';
import { buildSiemResponse } from '../../../routes/utils';
import { internalRuleToAPIResponse } from '../../../rule_management/normalization/rule_converters';
import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors';
import { performTimelinesInstallation } from '../../logic/perform_timelines_installation';
import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client';
import { createPrebuiltRuleObjectsClient } from '../../logic/rule_objects/prebuilt_rule_objects_client';
import { upgradePrebuiltRules } from '../../logic/rule_objects/upgrade_prebuilt_rules';
@ -45,7 +46,7 @@ export const performRuleUpgradeRoute = (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 = ctx.alerting.getRulesClient();
const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient);
@ -147,20 +148,32 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) =>
rulesClient,
targetRules
);
const combinedErrors = [...fetchErrors, ...installationErrors];
const ruleErrors = [...fetchErrors, ...installationErrors];
const { error: timelineInstallationError } = await performTimelinesInstallation(
ctx.securitySolution
);
const allErrors = aggregatePrebuiltRuleErrors(ruleErrors);
if (timelineInstallationError) {
allErrors.push({
message: timelineInstallationError,
rules: [],
});
}
const body: PerformRuleUpgradeResponseBody = {
summary: {
total: updatedRules.length + skippedRules.length + combinedErrors.length,
total: updatedRules.length + skippedRules.length + ruleErrors.length,
skipped: skippedRules.length,
succeeded: updatedRules.length,
failed: combinedErrors.length,
failed: ruleErrors.length,
},
results: {
updated: updatedRules.map(({ result }) => internalRuleToAPIResponse(result)),
skipped: skippedRules,
},
errors: aggregatePrebuiltRuleErrors(combinedErrors),
errors: allErrors,
};
return response.ok({ body });

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { validate } from '@kbn/securitysolution-io-ts-utils';
import { importTimelineResultSchema } from '../../../../../common/types/timeline/api';
import type { SecuritySolutionApiRequestHandlerContext } from '../../../../types';
import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines';
export const performTimelinesInstallation = async (
securitySolutionContext: SecuritySolutionApiRequestHandlerContext
) => {
const timeline = await installPrepackagedTimelines(
securitySolutionContext.getConfig()?.maxTimelineImportExportSize,
securitySolutionContext.getFrameworkRequest(),
true
);
const [result, error] = validate(timeline, importTimelineResultSchema);
return {
result,
error,
};
};