Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RAM][HTTP versioning] Version Bulk Enable Route #179778

Merged
merged 10 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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.
*/

export { bulkEnableBodySchema } from './schemas/latest';
export type { BulkEnableRulesRequestBody, BulkEnableRulesResponse } from './types/latest';

export { bulkEnableBodySchema as bulkEnableBodySchemaV1 } from './schemas/v1';
export type {
BulkEnableRulesRequestBody as BulkEnableRulesRequestBodyV1,
BulkEnableRulesResponse as BulkEnableRulesResponseV1,
} from './types/v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { schema } from '@kbn/config-schema';

export const bulkEnableBodySchema = schema.object({
filter: schema.maybe(schema.string()),
ids: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1, maxSize: 1000 })),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import { RuleParamsV1, RuleResponseV1 } from '../../../response';
import { bulkEnableBodySchemaV1 } from '..';

export type BulkEnableRulesRequestBody = TypeOf<typeof bulkEnableBodySchemaV1>;

interface BulkEnableOperationError {
message: string;
status?: number;
rule: {
id: string;
name: string;
};
}

export interface BulkEnableRulesResponse<Params extends RuleParamsV1 = never> {
body: {
rules: Array<RuleResponseV1<Params>>;
errors: BulkEnableOperationError[];
total: number;
task_ids_failed_to_be_enabled: string[];
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
* 2.0.
*/
import { AlertConsumers } from '@kbn/rule-data-utils';
import { RulesClient, ConstructorOptions } from '../rules_client';
import { RulesClient, ConstructorOptions } from '../../../../rules_client/rules_client';
import {
savedObjectsClientMock,
savedObjectsRepositoryMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { ruleTypeRegistryMock } from '../../../../rule_type_registry.mock';
import { alertingAuthorizationMock } from '../../../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { AlertingAuthorization } from '../../../../authorization/alerting_authorization';
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
import { getBeforeSetup, setGlobalDate } from './lib';
import { getBeforeSetup, setGlobalDate } from '../../../../rules_client/tests/lib';
import { loggerMock } from '@kbn/logging-mocks';
import { BulkUpdateTaskResult } from '@kbn/task-manager-plugin/server/task_scheduling';
import { ActionsClient } from '@kbn/actions-plugin/server';
Expand All @@ -41,23 +41,23 @@ import {
disabledRuleForBulkDisable1,
siemRuleForBulkOps1,
siemRuleForBulkOps2,
} from './test_helpers';
} from '../../../../rules_client/tests/test_helpers';
import { TaskStatus } from '@kbn/task-manager-plugin/server';
import { migrateLegacyActions } from '../lib';
import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
import { migrateLegacyActions } from '../../../../rules_client/lib';
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry';

jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => {
jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => {
return {
migrateLegacyActions: jest.fn(),
};
});

jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({
jest.mock('../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({
bulkMarkApiKeysForInvalidation: jest.fn(),
}));

jest.mock('../../application/rule/methods/get_schedule_frequency', () => ({
jest.mock('../get_schedule_frequency', () => ({
validateScheduleLimit: jest.fn(),
}));

Expand Down Expand Up @@ -169,7 +169,7 @@ describe('bulkEnableRules', () => {
rulesClientParams.getActionsClient.mockResolvedValue(actionsClient);
});

test('should enable two rule', async () => {
test('should enable two rules', async () => {
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
saved_objects: [enabledRuleForBulkOps1, enabledRuleForBulkOps2],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,43 @@
*/

import pMap from 'p-map';
import Boom from '@hapi/boom';
import { KueryNode, nodeBuilder } from '@kbn/es-query';
import { SavedObjectsBulkUpdateObject, SavedObjectsFindResult } from '@kbn/core/server';
import {
SavedObjectsBulkCreateObject,
SavedObjectsBulkUpdateObject,
SavedObjectsFindResult,
} from '@kbn/core/server';
import { withSpan } from '@kbn/apm-utils';
import { Logger } from '@kbn/core/server';
import { TaskManagerStartContract, TaskStatus } from '@kbn/task-manager-plugin/server';
import { TaskInstanceWithDeprecatedFields } from '@kbn/task-manager-plugin/server/task';
import { RawRule } from '../../types';
import { convertRuleIdsToKueryNode } from '../../lib';
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
import { bulkCreateRulesSo } from '../../../../data/rule';
import { RawRule, RawRuleAction } from '../../../../types';
import { RuleDomain, RuleParams } from '../../types';
import { convertRuleIdsToKueryNode } from '../../../../lib';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
import {
retryIfBulkOperationConflicts,
buildKueryNodeFilter,
getAndValidateCommonBulkOptions,
} from '../common';
import { getRuleCircuitBreakerErrorMessage } from '../../../common';
} from '../../../../rules_client/common';
import { getRuleCircuitBreakerErrorMessage, SanitizedRule } from '../../../../../common';
import {
getAuthorizationFilter,
checkAuthorizationAndGetTotal,
updateMeta,
createNewAPIKeySet,
migrateLegacyActions,
} from '../lib';
import { RulesClientContext, BulkOperationError, BulkOptions } from '../types';
import { validateScheduleLimit } from '../../application/rule/methods/get_schedule_frequency';
import { RuleAttributes } from '../../data/rule/types';
import {
transformRuleAttributesToRuleDomain,
transformRuleDomainToRule,
} from '../../application/rule/transforms';
import type { RuleParams } from '../../application/rule/types';
import { ruleDomainSchema } from '../../application/rule/schemas';
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
updateMetaAttributes,
} from '../../../../rules_client/lib';
import { RulesClientContext, BulkOperationError } from '../../../../rules_client/types';
import { validateScheduleLimit } from '../get_schedule_frequency';
import { RuleAttributes } from '../../../../data/rule/types';
import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects';
import { BulkEnableRulesParams, BulkEnableRulesResult } from './types';
import { bulkEnableRulesParamsSchema } from './schemas';
import { transformRuleAttributesToRuleDomain, transformRuleDomainToRule } from '../../transforms';
import { ruleDomainSchema } from '../../schemas';

const getShouldScheduleTask = async (
context: RulesClientContext,
Expand All @@ -63,13 +68,19 @@ const getShouldScheduleTask = async (
}
};

export const bulkEnableRules = async <Params extends RuleParams>(
export const bulkEnableRules = async <Params extends RuleParams = never>(
JiaweiWu marked this conversation as resolved.
Show resolved Hide resolved
context: RulesClientContext,
options: BulkOptions
) => {
const { ids, filter } = getAndValidateCommonBulkOptions(options);
params: BulkEnableRulesParams
): Promise<BulkEnableRulesResult<Params>> => {
const { ids, filter } = getAndValidateCommonBulkOptions(params);
const actionsClient = await context.getActionsClient();

try {
bulkEnableRulesParamsSchema.validate(params);
} catch (error) {
throw Boom.badRequest(`Error validating bulk enable rules data - ${error.message}`);
}

const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter);
const authorizationFilter = await getAuthorizationFilter(context, { action: 'ENABLE' });

Expand Down Expand Up @@ -99,12 +110,12 @@ export const bulkEnableRules = async <Params extends RuleParams>(
taskManager: context.taskManager,
});

const enabledRules = rules.map(({ id, attributes, references }) => {
const updatedRules = rules.map(({ id, attributes, references }) => {
// TODO (http-versioning): alertTypeId should never be null, but we need to
// fix the type cast from SavedObjectsBulkUpdateObject to SavedObjectsBulkUpdateObject
// when we are doing the bulk disable and this should fix itself
// when we are doing the bulk delete and this should fix itself
const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId!);
const ruleDomain = transformRuleAttributesToRuleDomain<Params>(
const ruleDomain: RuleDomain<Params> = transformRuleAttributesToRuleDomain<Params>(
attributes as RuleAttributes,
{
id,
Expand All @@ -119,12 +130,17 @@ export const bulkEnableRules = async <Params extends RuleParams>(
try {
ruleDomainSchema.validate(ruleDomain);
} catch (e) {
context.logger.warn(`Error validating bulk enabled rule domain object for id: ${id}, ${e}`);
context.logger.warn(`Error validating bulk enable rule domain object for id: ${id}, ${e}`);
}
return transformRuleDomainToRule(ruleDomain);
return ruleDomain;
});

return { errors, rules: enabledRules, total, taskIdsFailedToBeEnabled };
// // TODO (http-versioning): This should be of type Rule, change this when all rule types are fixed
const updatePublicRules = updatedRules.map((rule: RuleDomain<Params>) => {
return transformRuleDomainToRule<Params>(rule);
}) as Array<SanitizedRule<Params>>;
JiaweiWu marked this conversation as resolved.
Show resolved Hide resolved

return { errors, rules: updatePublicRules, total, taskIdsFailedToBeEnabled };
};

const bulkEnableRulesWithOCC = async (
Expand All @@ -139,7 +155,7 @@ const bulkEnableRulesWithOCC = async (
type: 'rules',
},
async () =>
await context.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser<RawRule>(
await context.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser<RuleAttributes>(
{
filter: filter ? nodeBuilder.and([filter, additionalFilter]) : additionalFilter,
type: RULE_SAVED_OBJECT_TYPE,
Expand All @@ -149,8 +165,8 @@ const bulkEnableRulesWithOCC = async (
)
);

const rulesFinderRules: Array<SavedObjectsFindResult<RawRule>> = [];
const rulesToEnable: Array<SavedObjectsBulkUpdateObject<RawRule>> = [];
const rulesFinderRules: Array<SavedObjectsFindResult<RuleAttributes>> = [];
const rulesToEnable: Array<SavedObjectsBulkUpdateObject<RuleAttributes>> = [];
const tasksToSchedule: TaskInstanceWithDeprecatedFields[] = [];
const errors: BulkOperationError[] = [];
const ruleNameToRuleIdMapping: Record<string, string> = {};
Expand Down Expand Up @@ -199,14 +215,15 @@ const bulkEnableRulesWithOCC = async (
ruleNameToRuleIdMapping[rule.id] = rule.attributes.name;
}

// TODO (http-versioning) Remove RawRuleAction and RawRule casts
const migratedActions = await migrateLegacyActions(context, {
ruleId: rule.id,
actions: rule.attributes.actions,
actions: rule.attributes.actions as RawRuleAction[],
references: rule.references,
attributes: rule.attributes,
attributes: rule.attributes as RawRule,
});

const updatedAttributes = updateMeta(context, {
const updatedAttributes = updateMetaAttributes(context, {
...rule.attributes,
...(!rule.attributes.apiKey &&
(await createNewAPIKeySet(context, {
Expand Down Expand Up @@ -303,12 +320,21 @@ const bulkEnableRulesWithOCC = async (
const result = await withSpan(
{ name: 'unsecuredSavedObjectsClient.bulkCreate', type: 'rules' },
() =>
context.unsecuredSavedObjectsClient.bulkCreate(rulesToEnable, {
overwrite: true,
// TODO (http-versioning): for whatever reasoning we are using SavedObjectsBulkUpdateObject
// everywhere when it should be SavedObjectsBulkCreateObject. We need to fix it in
// bulk_disable, bulk_enable, etc. to fix this cast
bulkCreateRulesSo({
savedObjectsClient: context.unsecuredSavedObjectsClient,
bulkCreateRuleAttributes: rulesToEnable as Array<
SavedObjectsBulkCreateObject<RuleAttributes>
>,
savedObjectsBulkCreateOptions: {
overwrite: true,
},
})
);

const rules: Array<SavedObjectsBulkUpdateObject<RawRule>> = [];
const rules: Array<SavedObjectsBulkUpdateObject<RuleAttributes>> = [];
const taskIdsToEnable: string[] = [];

result.saved_objects.forEach((rule) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

export type { BulkEnableRulesParams, BulkEnableRulesError, BulkEnableRulesResult } from './types';

export { bulkEnableRules } from './bulk_enable_rules';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { schema } from '@kbn/config-schema';

export const bulkEnableRulesParamsSchema = schema.object({
filter: schema.maybe(schema.string()),
ids: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1, maxSize: 1000 })),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './bulk_enable_rules_schemas';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { RuleParams } from '../../../types/rule';
import { SanitizedRule } from '../../../../../types';

export interface BulkEnableRulesParams {
filter?: string;
ids?: string[];
}

export interface BulkEnableRulesError {
message: string;
status?: number;
rule: {
id: string;
name: string;
};
}

// TODO (http-versioning): This should be of type Rule, change this when all rule types are fixed
export interface BulkEnableRulesResult<Params extends RuleParams = never> {
rules: Array<SanitizedRule<Params>>;
JiaweiWu marked this conversation as resolved.
Show resolved Hide resolved
errors: BulkEnableRulesError[];
total: number;
taskIdsFailedToBeEnabled: string[];
}
Loading