Skip to content

Commit

Permalink
Improve the case request
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Nov 10, 2023
1 parent c8e82e5 commit 1ead049
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ describe('CasesConnector', () => {
];

const groupingBy = ['host.name', 'dest.ip'];
const rule = { id: 'rule-test-id', name: 'Test rule', tags: ['rule', 'test'] };
const rule = {
id: 'rule-test-id',
name: 'Test rule',
tags: ['rule', 'test'],
ruleUrl: 'https://example.com/rules/rule-test-id',
};
const owner = 'cases';

const groupedAlertsWithOracleKey = [
Expand Down Expand Up @@ -261,7 +266,7 @@ describe('CasesConnector', () => {
});
});

it('creates non existing cases', async () => {
it('creates non existing cases correctly', async () => {
casesClientMock.cases.bulkCreate.mockResolvedValue({ cases: [cases[2]] });
casesClientMock.cases.bulkGet.mockResolvedValue({
cases: [cases[0], cases[1]],
Expand All @@ -286,13 +291,14 @@ describe('CasesConnector', () => {
expect(casesClientMock.cases.bulkCreate).toHaveBeenCalledWith({
cases: [
{
title: '',
description: '',
title: 'Test rule (Auto-created)',
description:
'This case is auto-created by [Test rule](https://example.com/rules/rule-test-id). \n\n Grouping: `host.name` equals `B` and `dest.ip` equals `0.0.0.3`',
owner: 'cases',
settings: {
syncAlerts: false,
},
tags: [],
tags: ['auto-generated', ...rule.tags],
connector: {
fields: null,
id: 'none',
Expand Down
93 changes: 68 additions & 25 deletions x-pack/plugins/cases/server/connectors/cases/cases_connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ export class CasesConnector extends SubActionConnector<
const { alerts, groupingBy } = params;
const casesClient = await this.casesParams.getCasesClient(this.kibanaRequest);

/**
* TODO: Handle when grouping is not defined
* One case should be created per rule
*/
const groupedAlerts = this.groupAlerts({ alerts, groupingBy });
const groupedAlertsWithOracleKey = this.generateOracleKeys(params, groupedAlerts);

Expand Down Expand Up @@ -203,23 +199,24 @@ export class CasesConnector extends SubActionConnector<
])
);

for (const error of bulkGetRecordsErrors) {
if (error.id && recordsMap.has(error.id)) {
/**
* TODO: Throw/retry for other errors
*/
const nonFoundErrors = bulkGetRecordsErrors.filter((error) => error.statusCode === 404);

for (const error of nonFoundErrors) {
if (error.id && error.statusCode === 404 && recordsMap.has(error.id)) {
bulkCreateReq.push({
recordId: error.id,
payload: recordsMap.get(error.id) ?? { cases: [], rules: [], grouping: {} },
});
}
}

/**
* TODO: Create records with only 404 errors
* All others should throw an error and retry again
*/
const bulkCreateRes = await this.casesOracleService.bulkCreateRecord(bulkCreateReq);

/**
* TODO: Retry on errors
* TODO: Throw/Retry on errors
*/
const [bulkCreateValidRecords, _] = partitionRecords(bulkCreateRes);

Expand Down Expand Up @@ -283,25 +280,25 @@ export class CasesConnector extends SubActionConnector<
}

/**
* TODO: Handle different type of errors
* TODO: Throw/retry for other errors
*/
for (const error of errors) {
if (groupedAlertsWithCaseId.has(error.caseId) && error.status === 404) {
bulkCreateReq.push({
description: '',
tags: [],
title: '',
connector: { id: 'none', name: 'none', type: ConnectorTypes.none, fields: null },
settings: { syncAlerts: false },
owner: params.owner,
});
const nonFoundErrors = errors.filter((error) => error.status === 404);

for (const error of nonFoundErrors) {
if (groupedAlertsWithCaseId.has(error.caseId)) {
const data = groupedAlertsWithCaseId.get(error.caseId) as GroupedAlertsWithCaseId;

bulkCreateReq.push(this.getCreateCaseRequest(params, data));
}
}

if (bulkCreateReq.length === 0) {
return casesMap;
}

/**
* TODO: bulkCreate throws an error. Retry on errors.
*/
const bulkCreateCasesResponse = await casesClient.cases.bulkCreate({ cases: bulkCreateReq });

for (const res of bulkCreateCasesResponse.cases) {
Expand All @@ -314,6 +311,55 @@ export class CasesConnector extends SubActionConnector<
return casesMap;
}

private getCreateCaseRequest(
params: CasesConnectorRunParams,
groupingData: GroupedAlertsWithCaseId
) {
const { grouping } = groupingData;

const ruleName = params.rule.ruleUrl
? `[${params.rule.name}](${params.rule.ruleUrl})`
: params.rule.name;

const groupingDescription = this.getGroupingDescription(grouping);

const description = `This case is auto-created by ${ruleName}. \n\n Grouping: ${groupingDescription}`;

const tags = Array.isArray(params.rule.tags) ? params.rule.tags : [];

/**
* TODO: Add grouping info to
*/
return {
description,
tags: ['auto-generated', ...tags],
/**
* TODO: Append the counter to the name
*/
title: `${params.rule.name} (Auto-created)`,
connector: { id: 'none', name: 'none', type: ConnectorTypes.none, fields: null },
/**
* Turn on for Security solution
*/
settings: { syncAlerts: false },
owner: params.owner,
};
}

private getGroupingDescription(grouping: GroupedAlerts['grouping']) {
/**
* TODO: Handle multi values
*/
return Object.entries(grouping)
.map(([key, value]) => {
const keyAsCodeBlock = `\`${key}\``;
const valueAsCodeBlock = `\`${value}\``;

return `${keyAsCodeBlock} equals ${valueAsCodeBlock}`;
})
.join(' and ');
}

private async attachAlertsToCases(
casesClient: CasesClient,
groupedAlertsWithCases: Map<string, GroupedAlertsWithCases>,
Expand All @@ -325,9 +371,6 @@ export class CasesConnector extends SubActionConnector<
groupedAlertsWithCases.values()
).map(({ theCase, alerts }) => ({
caseId: theCase.id,
/**
* TODO: Verify _id, _index
*/
attachments: alerts.map((alert) => ({
type: AttachmentType.alert,
alertId: alert._id,
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/cases/server/connectors/cases/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';

const AlertSchema = schema.recordOf(schema.string(), schema.any(), {
validate: (value) => {
if (!Object.hasOwn(value, 'id') || !Object.hasOwn(value, 'index')) {
if (!Object.hasOwn(value, '_id') || !Object.hasOwn(value, '_index')) {
return 'Alert ID and index must be defined';
}
},
Expand All @@ -27,6 +27,7 @@ const RuleSchema = schema.object({
* TODO: Verify limits
*/
tags: schema.arrayOf(schema.string({ minLength: 1, maxLength: 50 }), { minSize: 0, maxSize: 10 }),
ruleUrl: schema.nullable(schema.string()),
});

/**
Expand Down

0 comments on commit 1ead049

Please sign in to comment.