[8.18] [Security Solution] Allow prebuilt rules import and export (#212509) (#213419)

# Backport

This will backport the following commits from `main` to `8.18`:
- [[Security Solution] Allow prebuilt rules import and export
(#212509)](https://github.com/elastic/kibana/pull/212509)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Nikita
Indik","email":"nikita.indik@elastic.co"},"sourceCommit":{"committedDate":"2025-03-06T15:58:33Z","message":"[Security
Solution] Allow prebuilt rules import and export
(#212509)\n\n**Resolves:
https://github.com/elastic/security-team/issues/11502**\n(internal)\n\nThis
PR implements following changes and adds API integration tests
for\nthem:\n- [x] Users with any license can export prebuilt rules (with
enabled\nfeature flag)\n- [x] Users with Basic/Essentials license can
import prebuilt rules only\nif they are non-customized and the feature
flag is enabled\n- [x] Users with Enterprise/Complete license can import
prebuilt rules\nwithout restrictions\n\nFlaky test runner (had to create
4 separate runs to test all
configs):\n-\n[1](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7987)\n-\n[2](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7986)\n-\n[3](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7988)\n-\n[4](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7989)","sha":"ebe90e5c80e1dbe34d96ccd8a1e8e34d032affa4","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","Feature:Prebuilt Detection Rules","Feature:Rule
Import/Export","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[Security
Solution] Allow prebuilt rules import and
export","number":212509,"url":"https://github.com/elastic/kibana/pull/212509","mergeCommit":{"message":"[Security
Solution] Allow prebuilt rules import and export
(#212509)\n\n**Resolves:
https://github.com/elastic/security-team/issues/11502**\n(internal)\n\nThis
PR implements following changes and adds API integration tests
for\nthem:\n- [x] Users with any license can export prebuilt rules (with
enabled\nfeature flag)\n- [x] Users with Basic/Essentials license can
import prebuilt rules only\nif they are non-customized and the feature
flag is enabled\n- [x] Users with Enterprise/Complete license can import
prebuilt rules\nwithout restrictions\n\nFlaky test runner (had to create
4 separate runs to test all
configs):\n-\n[1](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7987)\n-\n[2](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7986)\n-\n[3](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7988)\n-\n[4](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7989)","sha":"ebe90e5c80e1dbe34d96ccd8a1e8e34d032affa4"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/212509","number":212509,"mergeCommit":{"message":"[Security
Solution] Allow prebuilt rules import and export
(#212509)\n\n**Resolves:
https://github.com/elastic/security-team/issues/11502**\n(internal)\n\nThis
PR implements following changes and adds API integration tests
for\nthem:\n- [x] Users with any license can export prebuilt rules (with
enabled\nfeature flag)\n- [x] Users with Basic/Essentials license can
import prebuilt rules only\nif they are non-customized and the feature
flag is enabled\n- [x] Users with Enterprise/Complete license can import
prebuilt rules\nwithout restrictions\n\nFlaky test runner (had to create
4 separate runs to test all
configs):\n-\n[1](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7987)\n-\n[2](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7986)\n-\n[3](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7988)\n-\n[4](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7989)","sha":"ebe90e5c80e1dbe34d96ccd8a1e8e34d032affa4"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Nikita Indik <nikita.indik@elastic.co>
This commit is contained in:
Kibana Machine 2025-03-07 04:53:47 +11:00 committed by GitHub
parent d24ab7c2c7
commit b38b24112b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 981 additions and 65 deletions

View file

@ -81,6 +81,13 @@ disabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_non_customized_prebuilt_rules/feature_enabled/configs/serverless_essentials_tier.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_non_customized_prebuilt_rules/feature_disabled/configs/serverless_feature_flag_disabled.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_customized_prebuilt_rules/feature_enabled/configs/serverless_complete_tier.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_customized_prebuilt_rules/feature_disabled/configs/serverless_essentials_tier.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_customized_prebuilt_rules/feature_disabled/configs/serverless_feature_flag_disabled.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/export_prebuilt_rules/feature_enabled/configs/serverless_essentials_tier.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/export_prebuilt_rules/feature_disabled/configs/serverless_feature_flag_disabled.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/configs/serverless.config.ts

View file

@ -70,6 +70,13 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_non_customized_prebuilt_rules/feature_enabled/configs/ess_basic_license.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_non_customized_prebuilt_rules/feature_disabled/configs/ess_feature_flag_disabled.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_customized_prebuilt_rules/feature_enabled/configs/ess_enterprise_license.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_customized_prebuilt_rules/feature_disabled/configs/ess_basic_license.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/import_customized_prebuilt_rules/feature_disabled/configs/ess_feature_flag_disabled.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/export_prebuilt_rules/feature_enabled/configs/ess_basic_license.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/export_prebuilt_rules/feature_disabled/configs/ess_feature_flag_disabled.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_management/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_read/basic_license_essentials_tier/configs/ess.config.ts

View file

@ -25,8 +25,8 @@ import { useDownloadExportedRules } from '../../../rule_management/logic/bulk_ac
import { useHasActionsPrivileges } from './use_has_actions_privileges';
import type { TimeRange } from '../../../rule_gaps/types';
import { useScheduleRuleRun } from '../../../rule_gaps/logic/use_schedule_rule_run';
import { usePrebuiltRulesCustomizationStatus } from '../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_customization_status';
import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
export const useRulesTableActions = ({
showExceptionsDuplicateConfirmation,
@ -47,7 +47,9 @@ export const useRulesTableActions = ({
const { bulkExport } = useBulkExport();
const downloadExportedRules = useDownloadExportedRules();
const { scheduleRuleRun } = useScheduleRuleRun();
const { isRulesCustomizationEnabled } = usePrebuiltRulesCustomizationStatus();
const isPrebuiltRulesCustomizationFeatureFlagEnabled = useIsExperimentalFeatureEnabled(
'prebuiltRulesCustomizationEnabled'
);
return [
{
@ -118,7 +120,7 @@ export const useRulesTableActions = ({
await downloadExportedRules(response);
}
},
enabled: (rule: Rule) => isRulesCustomizationEnabled || !rule.immutable,
enabled: (rule: Rule) => isPrebuiltRulesCustomizationFeatureFlagEnabled || !rule.immutable,
},
{
type: 'icon',

View file

@ -14,7 +14,6 @@ import {
} from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import { usePrebuiltRulesCustomizationStatus } from '../../../../detection_engine/rule_management/logic/prebuilt_rules/use_prebuilt_rules_customization_status';
import { useScheduleRuleRun } from '../../../../detection_engine/rule_gaps/logic/use_schedule_rule_run';
import type { TimeRange } from '../../../../detection_engine/rule_gaps/types';
import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants';
@ -36,6 +35,7 @@ import { useDownloadExportedRules } from '../../../../detection_engine/rule_mana
import * as i18nActions from '../../../pages/detection_engine/rules/translations';
import * as i18n from './translations';
import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
const MyEuiButtonIcon = styled(EuiButtonIcon)`
&.euiButtonIcon {
@ -73,7 +73,9 @@ const RuleActionsOverflowComponent = ({
application: { navigateToApp },
telemetry,
} = useKibana().services;
const { isRulesCustomizationEnabled } = usePrebuiltRulesCustomizationStatus();
const isPrebuiltRulesCustomizationFeatureFlagEnabled = useIsExperimentalFeatureEnabled(
'prebuiltRulesCustomizationEnabled'
);
const { startTransaction } = useStartTransaction();
const { executeBulkAction } = useExecuteBulkAction({ suppressSuccessToast: true });
const { bulkExport } = useBulkExport();
@ -140,7 +142,8 @@ const RuleActionsOverflowComponent = ({
key={i18nActions.EXPORT_RULE}
icon="exportAction"
disabled={
!userHasPermissions || (isRulesCustomizationEnabled === false && rule.immutable)
!userHasPermissions ||
(isPrebuiltRulesCustomizationFeatureFlagEnabled === false && rule.immutable)
}
data-test-subj="rules-details-export-rule"
onClick={async () => {
@ -210,7 +213,7 @@ const RuleActionsOverflowComponent = ({
rule,
canDuplicateRuleWithActions,
userHasPermissions,
isRulesCustomizationEnabled,
isPrebuiltRulesCustomizationFeatureFlagEnabled,
startTransaction,
closePopover,
showBulkDuplicateExceptionsConfirmation,

View file

@ -43,6 +43,7 @@ import { bulkEnableDisableRules } from './bulk_enable_disable_rules';
import { fetchRulesByQueryOrIds } from './fetch_rules_by_query_or_ids';
import { bulkScheduleBackfill } from './bulk_schedule_rule_run';
import { createPrebuiltRuleAssetsClient } from '../../../../prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client';
import { PrebuiltRulesCustomizationDisabledReason } from '../../../../../../../common/detection_engine/prebuilt_rules/prebuilt_rule_customization_status';
const MAX_RULES_TO_PROCESS_TOTAL = 10000;
// Set a lower limit for bulk edit as the rules client might fail with a "Query
@ -277,6 +278,14 @@ export const performBulkActionRoute = (
break;
}
case BulkActionTypeEnum.export: {
const prebuiltRulesCustomizationStatus =
detectionRulesClient.getRuleCustomizationStatus();
const isPrebuiltRulesExportAllowed =
prebuiltRulesCustomizationStatus.isRulesCustomizationEnabled ||
prebuiltRulesCustomizationStatus.customizationDisabledReason ===
PrebuiltRulesCustomizationDisabledReason.License;
const exported = await getExportByObjectIds(
rulesClient,
exceptionsClient,
@ -284,7 +293,7 @@ export const performBulkActionRoute = (
exporter,
request,
actionsClient,
detectionRulesClient.getRuleCustomizationStatus().isRulesCustomizationEnabled
isPrebuiltRulesExportAllowed
);
const responseBody = `${exported.rulesNdjson}${exported.exceptionLists}${exported.actionConnectors}${exported.exportDetails}`;

View file

@ -23,6 +23,7 @@ import { getExportByObjectIds } from '../../../logic/export/get_export_by_object
import { getExportAll } from '../../../logic/export/get_export_all';
import { buildSiemResponse } from '../../../../routes/utils';
import { RULE_MANAGEMENT_IMPORT_EXPORT_SOCKET_TIMEOUT_MS } from '../../timeouts';
import { PrebuiltRulesCustomizationDisabledReason } from '../../../../../../../common/detection_engine/prebuilt_rules/prebuilt_rule_customization_status';
export const exportRulesRoute = (
router: SecuritySolutionPluginRouter,
@ -73,7 +74,12 @@ export const exportRulesRoute = (
const client = getClient({ includedHiddenTypes: ['action'] });
const actionsExporter = getExporter(client);
const { isRulesCustomizationEnabled } = detectionRulesClient.getRuleCustomizationStatus();
const prebuiltRulesCustomizationStatus = detectionRulesClient.getRuleCustomizationStatus();
const isPrebuiltRulesExportAllowed =
prebuiltRulesCustomizationStatus.isRulesCustomizationEnabled ||
prebuiltRulesCustomizationStatus.customizationDisabledReason ===
PrebuiltRulesCustomizationDisabledReason.License;
try {
const exportSizeLimit = config.maxRuleImportExportSize;
@ -85,7 +91,7 @@ export const exportRulesRoute = (
} else {
let rulesCount = 0;
if (isRulesCustomizationEnabled) {
if (isPrebuiltRulesExportAllowed) {
rulesCount = await getRulesCount({
rulesClient,
filter: '',
@ -95,6 +101,7 @@ export const exportRulesRoute = (
rulesClient,
});
}
if (rulesCount > exportSizeLimit) {
return siemResponse.error({
statusCode: 400,
@ -112,7 +119,7 @@ export const exportRulesRoute = (
actionsExporter,
request,
actionsClient,
isRulesCustomizationEnabled
isPrebuiltRulesExportAllowed
)
: await getExportAll(
rulesClient,
@ -120,7 +127,7 @@ export const exportRulesRoute = (
actionsExporter,
request,
actionsClient,
isRulesCustomizationEnabled
isPrebuiltRulesExportAllowed
);
const responseBody = request.query.exclude_export_details

View file

@ -31,6 +31,7 @@ import { getQueryRuleParams } from '../../../../rule_schema/mocks';
import { importRulesRoute } from './route';
import { HttpAuthzError } from '../../../../../machine_learning/validation';
import { createPrebuiltRuleAssetsClient as createPrebuiltRuleAssetsClientMock } from '../../../../prebuilt_rules/logic/rule_assets/__mocks__/prebuilt_rule_assets_client';
import { PrebuiltRulesCustomizationDisabledReason } from '../../../../../../../common/detection_engine/prebuilt_rules/prebuilt_rule_customization_status';
jest.mock('../../../../../machine_learning/authz');
@ -59,6 +60,7 @@ describe('Import rules route', () => {
clients.detectionRulesClient.importRule.mockResolvedValue(getRulesSchemaMock());
clients.detectionRulesClient.getRuleCustomizationStatus.mockReturnValue({
isRulesCustomizationEnabled: false,
customizationDisabledReason: PrebuiltRulesCustomizationDisabledReason.FeatureFlag,
});
clients.actionsClient.getAll.mockResolvedValue([]);
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
@ -71,7 +73,6 @@ describe('Import rules route', () => {
describe('status codes', () => {
test('returns 200 when importing a single rule with a valid actionClient and alertClient', async () => {
const response = await server.inject(request, requestContextMock.convertContext(context));
expect(response.status).toEqual(200);
});

View file

@ -39,6 +39,7 @@ import {
migrateLegacyActionsIds,
} from '../../../utils/utils';
import { RULE_MANAGEMENT_IMPORT_EXPORT_SOCKET_TIMEOUT_MS } from '../../timeouts';
import { PrebuiltRulesCustomizationDisabledReason } from '../../../../../../../common/detection_engine/prebuilt_rules/prebuilt_rule_customization_status';
const CHUNK_PARSED_OBJECT_SIZE = 50;
@ -86,7 +87,7 @@ export const importRulesRoute = (router: SecuritySolutionPluginRouter, config: C
]);
const detectionRulesClient = ctx.securitySolution.getDetectionRulesClient();
const { isRulesCustomizationEnabled } = detectionRulesClient.getRuleCustomizationStatus();
const ruleCustomizationStatus = detectionRulesClient.getRuleCustomizationStatus();
const actionsClient = ctx.actions.getActionsClient();
const actionSOClient = ctx.core.savedObjects.getClient({
includedHiddenTypes: ['action'],
@ -166,15 +167,10 @@ export const importRulesRoute = (router: SecuritySolutionPluginRouter, config: C
let importRuleResponse: ImportRuleResponse[] = [];
if (isRulesCustomizationEnabled) {
importRuleResponse = await importRules({
ruleChunks,
overwriteRules: request.query.overwrite,
allowMissingConnectorSecrets: !!actionConnectors.length,
ruleSourceImporter,
detectionRulesClient,
});
} else {
if (
ruleCustomizationStatus.customizationDisabledReason ===
PrebuiltRulesCustomizationDisabledReason.FeatureFlag
) {
importRuleResponse = await importRulesLegacy({
ruleChunks,
overwriteRules: request.query.overwrite,
@ -182,6 +178,14 @@ export const importRulesRoute = (router: SecuritySolutionPluginRouter, config: C
detectionRulesClient,
savedObjectsClient,
});
} else {
importRuleResponse = await importRules({
ruleChunks,
overwriteRules: request.query.overwrite,
allowMissingConnectorSecrets: !!actionConnectors.length,
ruleSourceImporter,
detectionRulesClient,
});
}
const parseErrors = parsedRuleErrors.map((error) =>

View file

@ -104,7 +104,7 @@ export const validateBulkEditRule = async ({
}
// Rule customization is disabled; only certain actions can be applied to immutable rules
const canRuleBeEdited = istEditApplicableToImmutableRule(edit);
const canRuleBeEdited = isEditApplicableToImmutableRule(edit);
if (!canRuleBeEdited) {
await throwDryRunError(
() => invariant(canRuleBeEdited, "Elastic rule can't be edited"),
@ -120,7 +120,7 @@ export const validateBulkEditRule = async ({
/**
* add_rule_actions, set_rule_actions can be applied to prebuilt/immutable rules
*/
const istEditApplicableToImmutableRule = (edit: BulkActionEditPayload[]): boolean => {
const isEditApplicableToImmutableRule = (edit: BulkActionEditPayload[]): boolean => {
const applicableActions: BulkActionEditType[] = [
BulkActionEditTypeEnum.set_rule_actions,
BulkActionEditTypeEnum.add_rule_actions,

View file

@ -19,6 +19,7 @@ import {
import { checkRuleExceptionReferences } from '../../import/check_rule_exception_references';
import { getReferencedExceptionLists } from '../../import/gather_referenced_exceptions';
import type { IDetectionRulesClient } from '../detection_rules_client_interface';
import { PrebuiltRulesCustomizationDisabledReason } from '../../../../../../../common/detection_engine/prebuilt_rules/prebuilt_rule_customization_status';
/**
* Imports rules
@ -39,6 +40,8 @@ export const importRules = async ({
rules: RuleToImport[];
savedObjectsClient: SavedObjectsClientContract;
}): Promise<Array<RuleResponse | RuleImportErrorObject>> => {
const ruleCustomizationStatus = detectionRulesClient.getRuleCustomizationStatus();
const existingLists = await getReferencedExceptionLists({
rules,
savedObjectsClient,
@ -69,6 +72,25 @@ export const importRules = async ({
}
const { immutable, ruleSource } = ruleSourceImporter.calculateRuleSource(rule);
const isCustomized = (ruleSource.type === 'external' && ruleSource.is_customized) ?? false;
if (
isCustomized &&
ruleCustomizationStatus.customizationDisabledReason ===
PrebuiltRulesCustomizationDisabledReason.License
) {
return createRuleImportErrorObject({
message: i18n.translate(
'xpack.securitySolution.detectionEngine.rules.licenseInsufficientToImportCustomizedPrebuiltRule',
{
defaultMessage:
'Importing prebuilt rules is not supported if the they were modified. Upgrade your license to import modified prebuilt rules [rule_id: {ruleId}].',
values: { ruleId: rule.rule_id },
}
),
ruleId: rule.rule_id,
});
}
const [exceptionErrors, exceptions] = checkRuleExceptionReferences({
rule,

View file

@ -24,15 +24,14 @@ export const getExportAll = async (
actionsExporter: ISavedObjectsExporter,
request: KibanaRequest,
actionsClient: ActionsClient,
prebuiltRulesCustomizationEnabled?: boolean
isPrebuiltRulesExportAllowed?: boolean
): Promise<{
rulesNdjson: string;
exportDetails: string;
exceptionLists: string | null;
actionConnectors: string;
prebuiltRulesCustomizationEnabled?: boolean;
}> => {
const ruleAlertTypes = prebuiltRulesCustomizationEnabled
const ruleAlertTypes = isPrebuiltRulesExportAllowed
? await getRules({ rulesClient, filter: '' })
: await getNonPackagedRules({ rulesClient });
const rules = transformAlertsToRules(ruleAlertTypes);

View file

@ -30,19 +30,18 @@ export const getExportByObjectIds = async (
actionsExporter: ISavedObjectsExporter,
request: KibanaRequest,
actionsClient: ActionsClient,
prebuiltRulesCustomizationEnabled?: boolean
isPrebuiltRulesExportAllowed?: boolean
): Promise<{
rulesNdjson: string;
exportDetails: string;
exceptionLists: string | null;
actionConnectors: string;
prebuiltRulesCustomizationEnabled?: boolean;
}> =>
withSecuritySpan('getExportByObjectIds', async () => {
const rulesAndErrors = await fetchRulesByIds(
rulesClient,
ruleIds,
prebuiltRulesCustomizationEnabled
isPrebuiltRulesExportAllowed
);
const { rules, missingRuleIds } = rulesAndErrors;
@ -83,7 +82,7 @@ interface FetchRulesResult {
const fetchRulesByIds = async (
rulesClient: RulesClient,
ruleIds: string[],
prebuiltRulesCustomizationEnabled?: boolean
isPrebuiltRulesExportAllowed?: boolean
): Promise<FetchRulesResult> => {
// It's important to avoid too many clauses in the request otherwise ES will fail to process the request
// with `too_many_clauses` error (see https://github.com/elastic/kibana/issues/170015). The clauses limit
@ -117,7 +116,7 @@ const fetchRulesByIds = async (
return matchingRule != null &&
hasValidRuleType(matchingRule) &&
(prebuiltRulesCustomizationEnabled || matchingRule.params.immutable !== true)
(isPrebuiltRulesExportAllowed || matchingRule.params.immutable !== true)
? {
rule: transformRuleToExportableFormat(internalRuleToAPIResponse(matchingRule)),
}

View file

@ -16,7 +16,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName: 'Rules Management - Rule Patch Integration Tests - ESS Env - Basic License',
reportName:
'Rules Management - Rule Import/Export Integration Tests - ESS Env - Basic License',
},
};
}

View file

@ -11,6 +11,6 @@ export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Patch Integration Tests - Serverless Env - Essentials Tier ',
'Rules Management - Rule Import/Export Integration Tests - Serverless Env - Essentials Tier',
},
});

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.trial')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Prebuilt Rule Export Integration Tests - Customization disabled - ESS Env',
},
};
return testConfig;
}

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Prebuilt Rule Export Integration Tests - Customization disabled - Serverless Env',
},
});

View file

@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from 'expect';
import { BulkActionTypeEnum } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import {
binaryToString,
createPrebuiltRuleAssetSavedObjects,
createRuleAssetSavedObject,
deleteAllPrebuiltRuleAssets,
installPrebuiltRules,
} from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const securitySolutionApi = getService('securitySolutionApi');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI Prebuilt rule export - feature disabled', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
it("Export API - doesn't export prebuilt rules when the feature is disabled", async () => {
const ruleId = 'prebuilt-rule-1';
const ruleAsset = createRuleAssetSavedObject({ rule_id: ruleId, version: 1 });
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
await installPrebuiltRules(es, supertest);
const { body } = await securitySolutionApi
.exportRules({ query: {}, body: null })
.expect(200)
.parse(binaryToString);
const exportDetails = JSON.parse(body.toString());
expect(exportDetails).toMatchObject({
exported_rules_count: 0,
missing_rules: [], // Prebuilt rules are not in missing rules, even though they are not exported
});
});
it("Bulk actions export API - doesn't export prebuilt rules when the feature is disabled", async () => {
const ruleAsset = createRuleAssetSavedObject({ rule_id: 'prebuilt-rule-1', version: 1 });
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
await installPrebuiltRules(es, supertest);
const findResponse = await securitySolutionApi.findRules({ query: {} });
const installedRule = findResponse.body.data[0];
const { body } = await securitySolutionApi
.performRulesBulkAction({
query: {},
body: { action: BulkActionTypeEnum.export, ids: [installedRule.id] },
})
.expect(200)
.parse(binaryToString);
const exportDetails = JSON.parse(body.toString());
expect(exportDetails).toMatchObject({
exported_rules_count: 0,
missing_rules: [{ rule_id: 'prebuilt-rule-1' }],
});
});
});
};

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rules Management - Prebuilt rule export', function () {
loadTestFile(require.resolve('./export_prebuilt_rules_feature_disabled'));
});
}

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.trial')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Prebuilt Rule Export Integration Tests - Customization enabled - ESS Env',
},
};
testConfig.kbnTestServer.serverArgs = testConfig.kbnTestServer.serverArgs.map((arg: string) => {
// Override the default value of `--xpack.securitySolution.enableExperimental` to enable the prebuilt rules customization feature
if (arg.includes('--xpack.securitySolution.enableExperimental')) {
return `--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`;
}
return arg;
});
return testConfig;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Prebuilt Rule Export Integration Tests - Customization enabled - Serverless Env',
},
kbnTestServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`,
],
});

View file

@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from 'expect';
import { BulkActionTypeEnum } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import {
binaryToString,
createPrebuiltRuleAssetSavedObjects,
createRuleAssetSavedObject,
deleteAllPrebuiltRuleAssets,
installPrebuiltRules,
} from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const securitySolutionApi = getService('securitySolutionApi');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI Prebuilt rule export', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
it("Export API - exports prebuilt all rules if rule_id's are not specified", async () => {
const ruleId = 'prebuilt-rule-1';
const ruleAsset = createRuleAssetSavedObject({ rule_id: ruleId, version: 1 });
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
await installPrebuiltRules(es, supertest);
const { body } = await securitySolutionApi
.exportRules({ query: {}, body: null })
.expect(200)
.parse(binaryToString);
const [ruleJson, exportDetailsJson] = body.toString().split(/\n/);
expect(JSON.parse(ruleJson)).toMatchObject({
rule_id: ruleId,
rule_source: {
type: 'external',
is_customized: false,
},
});
expect(JSON.parse(exportDetailsJson)).toMatchObject({
exported_rules_count: 1,
missing_rules: [],
});
});
it('Export API - exports specified prebuilt rules', async () => {
const ruleId = 'prebuilt-rule-1';
const ruleAsset = createRuleAssetSavedObject({ rule_id: ruleId, version: 1 });
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
await installPrebuiltRules(es, supertest);
const { body } = await securitySolutionApi
.exportRules({
query: {},
body: {
objects: [{ rule_id: ruleId }],
},
})
.expect(200)
.parse(binaryToString);
const [ruleJson, exportDetailsJson] = body.toString().split(/\n/);
expect(JSON.parse(ruleJson)).toMatchObject({
rule_id: ruleId,
rule_source: {
type: 'external',
is_customized: false,
},
});
expect(JSON.parse(exportDetailsJson)).toMatchObject({
exported_rules_count: 1,
missing_rules: [],
});
});
it('Bulk actions export API - exports prebuilt rules', async () => {
const ruleAsset = createRuleAssetSavedObject({ rule_id: 'prebuilt-rule-1', version: 1 });
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
await installPrebuiltRules(es, supertest);
const findResponse = await securitySolutionApi.findRules({ query: {} });
const installedRule = findResponse.body.data[0];
const { body } = await securitySolutionApi
.performRulesBulkAction({
query: {},
body: { action: BulkActionTypeEnum.export, ids: [installedRule.id] },
})
.expect(200)
.parse(binaryToString);
const [ruleJson, exportDetailsJson] = body.toString().split(/\n/);
expect(JSON.parse(ruleJson)).toMatchObject({
id: installedRule.id,
rule_source: {
type: 'external',
is_customized: false,
},
});
expect(JSON.parse(exportDetailsJson)).toMatchObject({
missing_rules: [],
});
});
});
};

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rules Management - Prebuilt rule export', function () {
loadTestFile(require.resolve('./export_prebuilt_rules_feature_enabled'));
});
}

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.basic')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing customized prebuilt rules - Customization enabled - ESS Env',
},
};
testConfig.kbnTestServer.serverArgs = testConfig.kbnTestServer.serverArgs.map((arg: string) => {
// Override the default value of `--xpack.securitySolution.enableExperimental` to enable the prebuilt rules customization feature
if (arg.includes('--xpack.securitySolution.enableExperimental')) {
return `--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`;
}
return arg;
});
return testConfig;
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.basic')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing customized prebuilt rules - Customization disabled - ESS Env',
},
};
return testConfig;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing customized prebuilt rules - Customization enabled - Serverless Env',
},
kbnTestServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`,
],
});

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing customized prebuilt rules - Customization disabled - Serverless Env',
},
kbnTestServerArgs: [],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rules Management - Rule Import API - Customized prebuilt rules', function () {
loadTestFile(require.resolve('./not_allowed_importing_customized_prebuilt_rules'));
});
}

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from 'expect';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import { deleteAllPrebuiltRuleAssets, getCustomQueryRuleParams } from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
import { combineToNdJson } from '../../../../utils/combine_to_ndjson';
import { createPrebuiltRuleAssetSavedObjects, createRuleAssetSavedObject } from '../../../../utils';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const securitySolutionApi = getService('securitySolutionApi');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI Prebuilt rule import', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
it(`does NOT import customized prebuilt rules when rule customization is disabled`, async () => {
const ruleId = 'prebuilt-rule-to-be-customized';
const ruleParams = getCustomQueryRuleParams({
rule_id: ruleId,
// @ts-expect-error the API supports this param, but we only need it in {@link RuleToImport}
immutable: true,
rule_source: { type: 'external', is_customized: false },
version: 1,
});
const ruleAsset = createRuleAssetSavedObject(ruleParams);
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
// Customizing the rule before importing
const ndjson = combineToNdJson({ ...ruleParams, name: 'My customized rule' });
const { body } = await securitySolutionApi
.importRules({ query: {} })
.attach('file', Buffer.from(ndjson), 'rules.ndjson')
.expect(200);
expect(body).toMatchObject({
success: false,
errors: [
{
rule_id: 'prebuilt-rule-to-be-customized',
error: {
status_code: 400,
message: expect.stringContaining('rule_id: prebuilt-rule-to-be-customized]'),
},
},
],
});
});
});
};

View file

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from 'expect';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import {
createPrebuiltRuleAssetSavedObjects,
createRuleAssetSavedObject,
deleteAllPrebuiltRuleAssets,
getCustomQueryRuleParams,
} from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
import { combineToNdJson } from '../../../../utils/combine_to_ndjson';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const securitySolutionApi = getService('securitySolutionApi');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI Prebuilt rule import', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
it(`imports customized prebuilt rules`, async () => {
const ruleId = 'prebuilt-rule-to-be-customized';
const ruleParams = getCustomQueryRuleParams({
rule_id: ruleId,
// @ts-expect-error the API supports this param, but we only need it in {@link RuleToImport}
immutable: true,
rule_source: { type: 'external', is_customized: false },
version: 1,
});
const ruleAsset = createRuleAssetSavedObject(ruleParams);
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
// Customizing the rule before importing
const ndjson = combineToNdJson({ ...ruleParams, name: 'My customized rule' });
const { body } = await securitySolutionApi
.importRules({ query: {} })
.attach('file', Buffer.from(ndjson), 'rules.ndjson')
.expect(200);
expect(body).toMatchObject({
success: true,
errors: [],
});
const { body: importedRule } = await securitySolutionApi
.readRule({
query: { rule_id: ruleId },
})
.expect(200);
expect(importedRule).toMatchObject({
...ruleParams,
name: 'My customized rule',
rule_source: {
type: 'external',
is_customized: true,
},
});
});
});
};

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.trial')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing customized prebuilt rules - Customization enabled - ESS Env',
},
};
testConfig.kbnTestServer.serverArgs = testConfig.kbnTestServer.serverArgs.map((arg: string) => {
// Override the default value of `--xpack.securitySolution.enableExperimental` to enable the prebuilt rules customization feature
if (arg.includes('--xpack.securitySolution.enableExperimental')) {
return `--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`;
}
return arg;
});
return testConfig;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing customized prebuilt rules - Customization disabled - Serverless Env',
},
kbnTestServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`,
],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rules Management - Rule Import API - Customized prebuilt rules', function () {
loadTestFile(require.resolve('./allowed_importing_customized_prebuilt_rules'));
});
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.trial')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing non-customized prebuilt rules - Customization disabled - ESS Env',
},
};
return testConfig;
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing non-customized prebuilt rules - Customization disabled - Serverless Env',
},
kbnTestServerArgs: [],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rules Management - Rule Import API - Non-customized prebuilt rules', function () {
loadTestFile(require.resolve('./not_allowed_importing_non_customized_prebuilt_rules'));
});
}

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from 'expect';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import { deleteAllPrebuiltRuleAssets, getCustomQueryRuleParams } from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
import { combineToNdJson } from '../../../../utils/combine_to_ndjson';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const securitySolutionApi = getService('securitySolutionApi');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI Prebuilt rule import', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
it(`does NOT allow importing non-customized prebuilt rules`, async () => {
const ruleToImport = getCustomQueryRuleParams({
rule_id: 'non-customized-prebuilt-rule',
// @ts-expect-error the API supports this param, but we only need it in {@link RuleToImport}
immutable: true,
rule_source: { type: 'external', is_customized: false },
});
const ndjson = combineToNdJson(ruleToImport);
const { body } = await securitySolutionApi
.importRules({ query: {} })
.attach('file', Buffer.from(ndjson), 'rules.ndjson')
.expect(200);
expect(body).toMatchObject({
success: false,
errors: [
{
rule_id: 'non-customized-prebuilt-rule',
error: {
status_code: 400,
message:
'Importing prebuilt rules is not supported. To import this rule as a custom rule, first duplicate the rule and then export it. [rule_id: non-customized-prebuilt-rule]',
},
},
],
});
});
});
};

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from 'expect';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import {
createPrebuiltRuleAssetSavedObjects,
createRuleAssetSavedObject,
deleteAllPrebuiltRuleAssets,
getCustomQueryRuleParams,
} from '../../../../utils';
import { deleteAllRules } from '../../../../../../../common/utils/security_solution';
import { combineToNdJson } from '../../../../utils/combine_to_ndjson';
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const securitySolutionApi = getService('securitySolutionApi');
const supertest = getService('supertest');
const log = getService('log');
describe('@ess @serverless @skipInServerlessMKI Import - Customization Enabled', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllPrebuiltRuleAssets(es, log);
});
it(`imports non-customized prebuilt rules`, async () => {
const ruleId = 'prebuilt-rule';
const ruleParams = getCustomQueryRuleParams({
rule_id: ruleId,
// @ts-expect-error the API supports this param, but we only need it in {@link RuleToImport}
immutable: true,
rule_source: { type: 'external', is_customized: false },
version: 1,
});
const ruleAsset = createRuleAssetSavedObject(ruleParams);
await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]);
const ndjson = combineToNdJson(ruleParams);
const { body } = await securitySolutionApi
.importRules({ query: {} })
.attach('file', Buffer.from(ndjson), 'rules.ndjson')
.expect(200);
expect(body).toMatchObject({
success: true,
errors: [],
});
const { body: importedRule } = await securitySolutionApi
.readRule({
query: { rule_id: ruleId },
})
.expect(200);
expect(importedRule).toMatchObject(ruleParams);
});
});
};

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(
require.resolve('../../../../../../../config/ess/config.base.basic')
);
const testConfig = {
...functionalConfig.getAll(),
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing non-customized prebuilt rules - ESS Env',
},
};
testConfig.kbnTestServer.serverArgs = testConfig.kbnTestServer.serverArgs.map((arg: string) => {
// Override the default value of `--xpack.securitySolution.enableExperimental` to enable the prebuilt rules customization feature
if (arg.includes('--xpack.securitySolution.enableExperimental')) {
return `--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`;
}
return arg;
});
return testConfig;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials';
export default createTestConfig({
testFiles: [require.resolve('..')],
junit: {
reportName:
'Rules Management - Rule Import Integration Tests - Importing non-customized prebuilt rules - Serverless Env',
},
kbnTestServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'prebuiltRulesCustomizationEnabled',
])}`,
],
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Rules Management - Rule Import API - Non-customized prebuilt rules', function () {
loadTestFile(require.resolve('./allowed_importing_non_customized_prebuilt_rules'));
});
}

View file

@ -1625,36 +1625,6 @@ export default ({ getService }: FtrProviderContext): void => {
describe('supporting prebuilt rule customization', () => {
describe('compatibility with prebuilt rule fields', () => {
it('rejects rules with "immutable: true" when the feature flag is disabled', async () => {
const rule = getCustomQueryRuleParams({
rule_id: 'rule-immutable',
// @ts-expect-error the API supports this param, but we only need it in {@link RuleToImport}
immutable: true,
});
const ndjson = combineToNdJson(rule);
const { body } = await supertest
.post(DETECTION_ENGINE_RULES_IMPORT_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.attach('file', Buffer.from(ndjson), 'rules.ndjson')
.expect(200);
expect(body).toMatchObject({
success: false,
errors: [
{
rule_id: 'rule-immutable',
error: {
status_code: 400,
message:
'Importing prebuilt rules is not supported. To import this rule as a custom rule, first duplicate the rule and then export it. [rule_id: rule-immutable]',
},
},
],
});
});
it('imports custom rules alongside prebuilt rules when feature flag is disabled', async () => {
const ndjson = combineToNdJson(
getCustomQueryRuleParams({