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

[8.15] [Security Solution] Fix prebuilt rule duplication logic to copy related integrations and required fields from the original rule (#191065) #191493

Merged
merged 2 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -7,6 +7,7 @@

import { v4 as uuidv4 } from 'uuid';
import type { SanitizedRule } from '@kbn/alerting-plugin/common';

import type { RuleParams } from '../../../rule_schema';
import { duplicateRule } from './duplicate_rule';

Expand Down Expand Up @@ -35,13 +36,25 @@ describe('duplicateRule', () => {
meta: undefined,
maxSignals: 100,
responseActions: [],
relatedIntegrations: [],
requiredFields: [],
relatedIntegrations: [
{
package: 'aws',
version: '~1.2.3',
integration: 'route53',
},
],
requiredFields: [
{
name: 'event.action',
type: 'keyword',
ecs: true,
},
],
riskScore: 42,
riskScoreMapping: [],
severity: 'low',
severityMapping: [],
setup: 'Some setup guide.',
setup: `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`,
threat: [],
to: 'now',
references: [],
Expand Down Expand Up @@ -93,151 +106,64 @@ describe('duplicateRule', () => {
jest.clearAllMocks();
});

it('returns an object with fields copied from a given rule', async () => {
const rule = createTestRule();
const result = await duplicateRule({
rule,
});

expect(result).toEqual({
name: expect.anything(), // covered in a separate test
params: {
...rule.params,
ruleSource: {
type: 'internal',
},
ruleId: expect.anything(), // covered in a separate test
},
tags: rule.tags,
alertTypeId: rule.alertTypeId,
consumer: rule.consumer,
schedule: rule.schedule,
actions: rule.actions,
enabled: false, // covered in a separate test
});
});

it('appends [Duplicate] to the name', async () => {
const rule = createTestRule();
rule.name = 'PowerShell Keylogging Script';
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
name: 'PowerShell Keylogging Script [Duplicate]',
})
);
});

it('generates a new ruleId', async () => {
const rule = createTestRule();
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
ruleId: 'new ruleId',
}),
})
);
});

it('makes sure the duplicated rule is disabled', async () => {
const rule = createTestRule();
rule.enabled = true;
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
enabled: false,
})
);
});

describe('when duplicating a prebuilt (immutable) rule', () => {
const createPrebuiltRule = () => {
describe('when duplicating any kind of rule', () => {
it('appends [Duplicate] to the name', async () => {
const rule = createTestRule();
rule.params.immutable = true;
return rule;
};

it('transforms it to a custom (mutable) rule', async () => {
const rule = createPrebuiltRule();
rule.name = 'PowerShell Keylogging Script';
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
immutable: false,
}),
name: 'PowerShell Keylogging Script [Duplicate]',
})
);
});

it('resets related integrations to an empty array', async () => {
const rule = createPrebuiltRule();
rule.params.relatedIntegrations = [
{
package: 'aws',
version: '~1.2.3',
integration: 'route53',
},
];

it('generates a new ruleId', async () => {
const rule = createTestRule();
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
relatedIntegrations: [],
ruleId: 'new ruleId',
}),
})
);
});

it('resets required fields to an empty array', async () => {
const rule = createPrebuiltRule();
rule.params.requiredFields = [
{
name: 'event.action',
type: 'keyword',
ecs: true,
},
];

it('makes sure the duplicated rule is disabled', async () => {
const rule = createTestRule();
rule.enabled = true;
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
requiredFields: [],
}),
enabled: false,
})
);
});
});

describe('when duplicating a custom (mutable) rule', () => {
const createCustomRule = () => {
describe('when duplicating a prebuilt rule', () => {
const createPrebuiltRule = () => {
const rule = createTestRule();
rule.params.immutable = false;
rule.params.immutable = true;
rule.params.ruleSource = {
type: 'external',
isCustomized: false,
};
return rule;
};

it('keeps it custom', async () => {
const rule = createCustomRule();
it('transforms it to a custom rule', async () => {
const rule = createPrebuiltRule();
const result = await duplicateRule({
rule,
});
Expand All @@ -246,71 +172,85 @@ describe('duplicateRule', () => {
expect.objectContaining({
params: expect.objectContaining({
immutable: false,
ruleSource: {
type: 'internal',
},
}),
})
);
});

it('copies related integrations as is', async () => {
const rule = createCustomRule();
rule.params.relatedIntegrations = [
{
package: 'aws',
version: '~1.2.3',
integration: 'route53',
},
];

it('copies fields from the original rule', async () => {
const rule = createPrebuiltRule();
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
relatedIntegrations: rule.params.relatedIntegrations,
}),
})
);
expect(result).toEqual({
name: expect.anything(), // covered in a separate test
params: {
...rule.params,
ruleId: expect.anything(), // covered in a separate test
immutable: expect.anything(), // covered in a separate test
ruleSource: expect.anything(), // covered in a separate test
},
tags: rule.tags,
alertTypeId: rule.alertTypeId,
consumer: rule.consumer,
schedule: rule.schedule,
actions: rule.actions,
enabled: false, // covered in a separate test
});
});
});

it('copies required fields as is', async () => {
const rule = createCustomRule();
rule.params.requiredFields = [
{
name: 'event.action',
type: 'keyword',
ecs: true,
},
];
describe('when duplicating a custom rule', () => {
const createCustomRule = () => {
const rule = createTestRule();
rule.params.immutable = false;
rule.params.ruleSource = {
type: 'internal',
};
return rule;
};

it('keeps it custom', async () => {
const rule = createCustomRule();
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
requiredFields: rule.params.requiredFields,
immutable: false,
ruleSource: {
type: 'internal',
},
}),
})
);
});

it('copies setup guide as is', async () => {
it('copies fields from the original rule', async () => {
const rule = createCustomRule();
rule.params.setup = `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`;
const result = await duplicateRule({
rule,
});

expect(result).toEqual(
expect.objectContaining({
params: expect.objectContaining({
setup: rule.params.setup,
}),
})
);
expect(result).toEqual({
name: expect.anything(), // covered in a separate test
params: {
...rule.params,
ruleId: expect.anything(), // covered in a separate test
},
tags: rule.tags,
alertTypeId: rule.alertTypeId,
consumer: rule.consumer,
schedule: rule.schedule,
actions: rule.actions,
enabled: false, // covered in a separate test
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type { SanitizedRule } from '@kbn/alerting-plugin/common';
import { SERVER_APP_ID } from '../../../../../../common/constants';
import type { InternalRuleCreate, RuleParams } from '../../../rule_schema';
import { transformToActionFrequency } from '../../normalization/rule_actions';
import { convertImmutableToRuleSource } from '../../normalization/rule_converters';

const DUPLICATE_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.cloneRule.duplicateTitle',
Expand All @@ -27,17 +26,18 @@ interface DuplicateRuleParams {

export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise<InternalRuleCreate> => {
// Generate a new static ruleId
const ruleId = uuidv4();

// If it's a prebuilt rule, reset Related Integrations, Required Fields and Setup Guide.
// We do this because for now we don't allow the users to edit these fields for custom rules.
const isPrebuilt = rule.params.immutable;
const relatedIntegrations = isPrebuilt ? [] : rule.params.relatedIntegrations;
const requiredFields = isPrebuilt ? [] : rule.params.requiredFields;
const actions = transformToActionFrequency(rule.actions, rule.throttle);
const ruleId: InternalRuleCreate['params']['ruleId'] = uuidv4();

// Duplicated rules are always considered custom rules
const immutable = false;
const immutable: InternalRuleCreate['params']['immutable'] = false;
const ruleSource: InternalRuleCreate['params']['ruleSource'] = {
type: 'internal',
};

const actions: InternalRuleCreate['actions'] = transformToActionFrequency(
rule.actions,
rule.throttle
);

return {
name: `${rule.name} [${DUPLICATE_TITLE}]`,
Expand All @@ -46,11 +46,9 @@ export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise<Inte
consumer: SERVER_APP_ID,
params: {
...rule.params,
immutable,
ruleSource: convertImmutableToRuleSource(immutable),
ruleId,
relatedIntegrations,
requiredFields,
immutable,
ruleSource,
exceptionsList: [],
},
schedule: rule.schedule,
Expand Down