Skip to content

Commit

Permalink
[Security Solution] Add timelines installation to the new rule upgrad…
Browse files Browse the repository at this point in the history
…e/install endpoints (#159694)

**Resolves: #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`
  • Loading branch information
xcrzx authored Jun 15, 2023
1 parent 25bf774 commit 16193c6
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}
Expand All @@ -116,33 +111,27 @@ 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);

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(
prebuiltRulesOutput,
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
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
};
};

0 comments on commit 16193c6

Please sign in to comment.