Skip to content

Commit

Permalink
[8.15] [Security Solution] Fix prebuilt rule duplication logic to cop…
Browse files Browse the repository at this point in the history
…y related integrations and required fields from the original rule (#191065) (#191493)

# Backport

This will backport the following commits from `main` to `8.15`:
- [[Security Solution] Fix prebuilt rule duplication logic to copy
related integrations and required fields from the original rule
(#191065)](#191065)

<!--- Backport version: 8.9.8 -->

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

<!--BACKPORT [{"author":{"name":"Georgii
Gorbachev","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-08-26T13:42:52Z","message":"[Security
Solution] Fix prebuilt rule duplication logic to copy related
integrations and required fields from the original rule
(#191065)\n\n**Fixes:
https://github.com/elastic/kibana/issues/190628**\r\n**Related to:**
https://github.com/elastic/kibana/issues/173595,\r\nhttps://github.com/elastic/kibana/issues/173594\r\n\r\n##
Summary\r\n\r\nAs stated in the bug ticket, when duplicating a prebuilt
rule, the\r\n\"Related Integrations\" and \"Required Fields\" values
should be inherited\r\nfrom the original rule, as it was specified in
the Acceptance Criteria\r\nfor
#173595
and\r\nhttps://github.com//issues/173594.\r\n\r\nThis
PR:\r\n\r\n- Removes the logic that resets these fields to empty arrays
for\r\nduplicated prebuilt rules - we needed this logic in the past
because\r\nthese fields were not editable in the UI, but we don't need
it anymore.\r\n- Updates the corresponding unit tests.\r\n\r\n##
Screenshots\r\n\r\nThese screenshots were taken after introducing the
fixes.\r\n\r\n**Original prebuilt rule:**\r\n\r\n<img width=\"1463\"
alt=\"Screenshot_2024-08-23_at_13_25_07\"\r\nsrc=\"https://github.com/user-attachments/assets/ad8673f5-aba3-40c8-ae91-bbd7d334b119\">\r\n\r\n**Duplicated
prebuilt rule:**\r\n\r\n<img width=\"1469\"
alt=\"Screenshot_2024-08-23_at_13_25_43\"\r\nsrc=\"https://github.com/user-attachments/assets/03761a2b-6f53-4bab-bf4c-a71c6860802b\">\r\n\r\n###
Checklist\r\n\r\n- [
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"b144c05e8f39f28dd9551b7c62daa01cfa1d2cd5","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","impact:medium","Team:Detections
and Resp","Team: SecuritySolution","Feature:Rule
Management","Team:Detection Rule Management","Feature:Prebuilt Detection
Rules","v8.16.0","v8.15.1"],"number":191065,"url":"https://github.com/elastic/kibana/pull/191065","mergeCommit":{"message":"[Security
Solution] Fix prebuilt rule duplication logic to copy related
integrations and required fields from the original rule
(#191065)\n\n**Fixes:
https://github.com/elastic/kibana/issues/190628**\r\n**Related to:**
https://github.com/elastic/kibana/issues/173595,\r\nhttps://github.com/elastic/kibana/issues/173594\r\n\r\n##
Summary\r\n\r\nAs stated in the bug ticket, when duplicating a prebuilt
rule, the\r\n\"Related Integrations\" and \"Required Fields\" values
should be inherited\r\nfrom the original rule, as it was specified in
the Acceptance Criteria\r\nfor
#173595
and\r\nhttps://github.com//issues/173594.\r\n\r\nThis
PR:\r\n\r\n- Removes the logic that resets these fields to empty arrays
for\r\nduplicated prebuilt rules - we needed this logic in the past
because\r\nthese fields were not editable in the UI, but we don't need
it anymore.\r\n- Updates the corresponding unit tests.\r\n\r\n##
Screenshots\r\n\r\nThese screenshots were taken after introducing the
fixes.\r\n\r\n**Original prebuilt rule:**\r\n\r\n<img width=\"1463\"
alt=\"Screenshot_2024-08-23_at_13_25_07\"\r\nsrc=\"https://github.com/user-attachments/assets/ad8673f5-aba3-40c8-ae91-bbd7d334b119\">\r\n\r\n**Duplicated
prebuilt rule:**\r\n\r\n<img width=\"1469\"
alt=\"Screenshot_2024-08-23_at_13_25_43\"\r\nsrc=\"https://github.com/user-attachments/assets/03761a2b-6f53-4bab-bf4c-a71c6860802b\">\r\n\r\n###
Checklist\r\n\r\n- [
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"b144c05e8f39f28dd9551b7c62daa01cfa1d2cd5"}},"sourceBranch":"main","suggestedTargetBranches":["8.15"],"targetPullRequestStates":[{"branch":"main","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/191065","number":191065,"mergeCommit":{"message":"[Security
Solution] Fix prebuilt rule duplication logic to copy related
integrations and required fields from the original rule
(#191065)\n\n**Fixes:
https://github.com/elastic/kibana/issues/190628**\r\n**Related to:**
https://github.com/elastic/kibana/issues/173595,\r\nhttps://github.com/elastic/kibana/issues/173594\r\n\r\n##
Summary\r\n\r\nAs stated in the bug ticket, when duplicating a prebuilt
rule, the\r\n\"Related Integrations\" and \"Required Fields\" values
should be inherited\r\nfrom the original rule, as it was specified in
the Acceptance Criteria\r\nfor
#173595
and\r\nhttps://github.com//issues/173594.\r\n\r\nThis
PR:\r\n\r\n- Removes the logic that resets these fields to empty arrays
for\r\nduplicated prebuilt rules - we needed this logic in the past
because\r\nthese fields were not editable in the UI, but we don't need
it anymore.\r\n- Updates the corresponding unit tests.\r\n\r\n##
Screenshots\r\n\r\nThese screenshots were taken after introducing the
fixes.\r\n\r\n**Original prebuilt rule:**\r\n\r\n<img width=\"1463\"
alt=\"Screenshot_2024-08-23_at_13_25_07\"\r\nsrc=\"https://github.com/user-attachments/assets/ad8673f5-aba3-40c8-ae91-bbd7d334b119\">\r\n\r\n**Duplicated
prebuilt rule:**\r\n\r\n<img width=\"1469\"
alt=\"Screenshot_2024-08-23_at_13_25_43\"\r\nsrc=\"https://github.com/user-attachments/assets/03761a2b-6f53-4bab-bf4c-a71c6860802b\">\r\n\r\n###
Checklist\r\n\r\n- [
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"b144c05e8f39f28dd9551b7c62daa01cfa1d2cd5"}},{"branch":"8.15","label":"v8.15.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
  • Loading branch information
banderror authored Aug 27, 2024
1 parent 267656f commit ed44e9a
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 160 deletions.
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

0 comments on commit ed44e9a

Please sign in to comment.