Skip to content

Commit

Permalink
Adding tests and fixing errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan-buttner committed Aug 18, 2021
1 parent 72379b7 commit c4a86fc
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 66 deletions.
159 changes: 124 additions & 35 deletions x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('ITSM formatter', () => {
} as CaseResponse;

it('it formats correctly without alerts', async () => {
const res = await format(theCase, []);
const res = format(theCase, []);
expect(res).toEqual({
dest_ip: null,
source_ip: null,
Expand All @@ -38,7 +38,7 @@ describe('ITSM formatter', () => {

it('it formats correctly when fields do not exist ', async () => {
const invalidFields = { connector: { fields: null } } as CaseResponse;
const res = await format(invalidFields, []);
const res = format(invalidFields, []);
expect(res).toEqual({
dest_ip: null,
source_ip: null,
Expand All @@ -55,25 +55,31 @@ describe('ITSM formatter', () => {
{
id: 'alert-1',
index: 'index-1',
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
source: {
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
url: { full: 'https://attack.com' },
},
url: { full: 'https://attack.com' },
},
{
id: 'alert-2',
index: 'index-2',
destination: { ip: '192.168.1.4' },
source: { ip: '192.168.1.3' },
file: {
hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
source: {
source: {
ip: '192.168.1.3',
},
destination: { ip: '192.168.1.4' },
file: {
hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
},
url: { full: 'https://attack.com/api' },
},
url: { full: 'https://attack.com/api' },
},
];
const res = await format(theCase, alerts);
const res = format(theCase, alerts);
expect(res).toEqual({
dest_ip: '192.168.1.1,192.168.1.4',
source_ip: '192.168.1.2,192.168.1.3',
Expand All @@ -86,30 +92,109 @@ describe('ITSM formatter', () => {
});
});

it('it ignores alerts with an error', async () => {
const alerts = [
{
id: 'alert-1',
index: 'index-1',
error: new Error('an error'),
source: {
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
url: { full: 'https://attack.com' },
},
},
{
id: 'alert-2',
index: 'index-2',
source: {
source: {
ip: '192.168.1.3',
},
destination: { ip: '192.168.1.4' },
file: {
hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
},
url: { full: 'https://attack.com/api' },
},
},
];
const res = format(theCase, alerts);
expect(res).toEqual({
dest_ip: '192.168.1.4',
source_ip: '192.168.1.3',
category: 'Denial of Service',
subcategory: 'Inbound DDos',
malware_hash: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752',
malware_url: 'https://attack.com/api',
priority: '2 - High',
});
});

it('it ignores alerts without a source field', async () => {
const alerts = [
{
id: 'alert-1',
index: 'index-1',
},
{
id: 'alert-2',
index: 'index-2',
source: {
source: {
ip: '192.168.1.3',
},
destination: { ip: '192.168.1.4' },
file: {
hash: { sha256: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752' },
},
url: { full: 'https://attack.com/api' },
},
},
];
const res = format(theCase, alerts);
expect(res).toEqual({
dest_ip: '192.168.1.4',
source_ip: '192.168.1.3',
category: 'Denial of Service',
subcategory: 'Inbound DDos',
malware_hash: '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752',
malware_url: 'https://attack.com/api',
priority: '2 - High',
});
});

it('it handles duplicates correctly', async () => {
const alerts = [
{
id: 'alert-1',
index: 'index-1',
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
source: {
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
url: { full: 'https://attack.com' },
},
url: { full: 'https://attack.com' },
},
{
id: 'alert-2',
index: 'index-2',
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.3' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
source: {
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.3' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
url: { full: 'https://attack.com/api' },
},
url: { full: 'https://attack.com/api' },
},
];
const res = await format(theCase, alerts);
const res = format(theCase, alerts);
expect(res).toEqual({
dest_ip: '192.168.1.1',
source_ip: '192.168.1.2,192.168.1.3',
Expand All @@ -126,22 +211,26 @@ describe('ITSM formatter', () => {
{
id: 'alert-1',
index: 'index-1',
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
source: {
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.2' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
url: { full: 'https://attack.com' },
},
url: { full: 'https://attack.com' },
},
{
id: 'alert-2',
index: 'index-2',
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.3' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
source: {
destination: { ip: '192.168.1.1' },
source: { ip: '192.168.1.3' },
file: {
hash: { sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08' },
},
url: { full: 'https://attack.com/api' },
},
url: { full: 'https://attack.com/api' },
},
];

Expand All @@ -150,7 +239,7 @@ describe('ITSM formatter', () => {
connector: { fields: { ...theCase.connector.fields, destIp: false, malwareHash: false } },
} as CaseResponse;

const res = await format(newCase, alerts);
const res = format(newCase, alerts);
expect(res).toEqual({
dest_ip: null,
source_ip: '192.168.1.2,192.168.1.3',
Expand Down
62 changes: 43 additions & 19 deletions x-pack/plugins/cases/server/services/alerts/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 2.0.
*/

import { KibanaRequest } from 'kibana/server';
import { CaseStatuses } from '../../../common';
import { AlertService, AlertServiceContract } from '.';
import { loggingSystemMock } from 'src/core/server/mocks';
Expand All @@ -15,23 +14,22 @@ import { PublicMethodsOf } from '@kbn/utility-types';

describe('updateAlertsStatus', () => {
const logger = loggingSystemMock.create().get('case');
let alertsClient: jest.Mocked<PublicMethodsOf<AlertsClient>>;
let alertService: AlertServiceContract;

beforeEach(async () => {
alertsClient = ruleRegistryMocks.createAlertsClientMock.create();
alertService = new AlertService(alertsClient);
jest.restoreAllMocks();
});

describe('happy path', () => {
let alertsClient: jest.Mocked<PublicMethodsOf<AlertsClient>>;
let alertService: AlertServiceContract;
const args = {
alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }],
request: {} as KibanaRequest,
logger,
};

beforeEach(async () => {
alertsClient = ruleRegistryMocks.createAlertsClientMock.create();
alertService = new AlertService(alertsClient);
jest.restoreAllMocks();
});

test('it update the status of the alert correctly', async () => {
it('updates the status of the alert correctly', async () => {
await alertService.updateAlertsStatus(args);

expect(alertsClient.update).toHaveBeenCalledWith({
Expand All @@ -41,15 +39,41 @@ describe('updateAlertsStatus', () => {
});
});

describe('unhappy path', () => {
it('ignores empty indices', async () => {
expect(
await alertService.updateAlertsStatus({
alerts: [{ id: 'alert-id-1', index: '', status: CaseStatuses.closed }],
logger,
})
).toBeUndefined();
it('translates the in-progress status to acknowledged', async () => {
await alertService.updateAlertsStatus({
alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses['in-progress'] }],
logger,
});

expect(alertsClient.update).toHaveBeenCalledWith({
id: 'alert-id-1',
index: '.siem-signals',
status: 'acknowledged',
});
});

it('defaults an unknown status to open', async () => {
await alertService.updateAlertsStatus({
alerts: [{ id: 'alert-id-1', index: '.siem-signals', status: 'bananas' as CaseStatuses }],
logger,
});

expect(alertsClient.update).toHaveBeenCalledWith({
id: 'alert-id-1',
index: '.siem-signals',
status: 'open',
});
});
});

describe('unhappy path', () => {
it('ignores empty indices', async () => {
expect(
await alertService.updateAlertsStatus({
alerts: [{ id: 'alert-id-1', index: '', status: CaseStatuses.closed }],
logger,
})
).toBeUndefined();
});
});
});
36 changes: 33 additions & 3 deletions x-pack/plugins/cases/server/services/alerts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { isEmpty } from 'lodash';
import type { PublicMethodsOf } from '@kbn/utility-types';

import { Logger } from 'kibana/server';
import { MAX_ALERTS_PER_SUB_CASE } from '../../../common';
import { CaseStatuses, MAX_ALERTS_PER_SUB_CASE } from '../../../common';
import { AlertInfo, createCaseError } from '../../common';
import { UpdateAlertRequest } from '../../client/alerts/types';
import { AlertsClient } from '../../../../rule_registry/server';
import { Alert } from './types';
import { STATUS_VALUES } from '../../../../rule_registry/common/technical_rule_data_field_names';

export type AlertServiceContract = PublicMethodsOf<AlertService>;

Expand Down Expand Up @@ -50,8 +51,13 @@ export class AlertService {
}

const updatedAlerts = await Promise.allSettled(
alertsToUpdate.map(({ id, index, status }) =>
this.alertsClient?.update({ id, index, status, _version: undefined })
alertsToUpdate.map((alert) =>
this.alertsClient?.update({
id: alert.id,
index: alert.index,
status: translateStatus({ alert, logger }),
_version: undefined,
})
)
);

Expand Down Expand Up @@ -127,3 +133,27 @@ export class AlertService {
}
}
}

function translateStatus({
alert,
logger,
}: {
alert: UpdateAlertRequest;
logger: Logger;
}): STATUS_VALUES {
const translatedStatuses: Record<string, STATUS_VALUES> = {
[CaseStatuses.open]: 'open',
[CaseStatuses['in-progress']]: 'acknowledged',
[CaseStatuses.closed]: 'closed',
};

const translatedStatus = translatedStatuses[alert.status];
if (!translatedStatus) {
logger.error(
`Unable to translate case status ${alert.status} during alert update: ${JSON.stringify(
alert
)}`
);
}
return translatedStatus ?? 'open';
}
Loading

0 comments on commit c4a86fc

Please sign in to comment.