Skip to content

Commit

Permalink
[Cases][SecOps] Fix bug where observables are not created (#133752)
Browse files Browse the repository at this point in the history
* Push observables:

* Filter docs without _source
  • Loading branch information
cnasikas authored Jun 9, 2022
1 parent f5faacd commit 47ffdd8
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 16 deletions.
138 changes: 138 additions & 0 deletions x-pack/plugins/cases/server/client/alerts/get.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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 { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { AlertService } from '../../services';
import { CasesClientArgs } from '../types';
import { getAlerts } from './get';

describe('getAlerts', () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();
const logger = loggingSystemMock.create().get('case');
let alertsService: AlertService;

beforeEach(async () => {
alertsService = new AlertService(esClient, logger);
jest.clearAllMocks();
});

const docs = [
{
_index: '.internal.alerts-security.alerts-default-000001',
_id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
_version: 2,
_seq_no: 255,
_primary_term: 1,
found: true,
_source: {
destination: { mac: 'ff:ff:ff:ff:ff:ff' },
source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 },
ecs: { version: '8.0.0' },
},
},
];

esClient.mget.mockResolvedValue({ docs });

it('returns an empty array if the alert info are empty', async () => {
const clientArgs = { alertsService } as unknown as CasesClientArgs;
const res = await getAlerts([], clientArgs);

expect(res).toEqual([]);
});

it('returns the alerts correctly', async () => {
const clientArgs = { alertsService } as unknown as CasesClientArgs;
const res = await getAlerts(
[
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
},
],
clientArgs
);

expect(res).toEqual([
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
destination: { mac: 'ff:ff:ff:ff:ff:ff' },
source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 },
ecs: { version: '8.0.0' },
},
]);
});

it('filters mget errors correctly', async () => {
esClient.mget.mockResolvedValue({
docs: [
...docs,
{
error: { type: 'not-found', reason: 'an error' },
_index: '.internal.alerts-security.alerts-default-000002',
_id: 'd3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
},
],
});
const clientArgs = { alertsService } as unknown as CasesClientArgs;

const res = await getAlerts(
[
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
},
],
clientArgs
);

expect(res).toEqual([
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
destination: { mac: 'ff:ff:ff:ff:ff:ff' },
source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 },
ecs: { version: '8.0.0' },
},
]);
});

it('filters docs without _source correctly', async () => {
esClient.mget.mockResolvedValue({
docs: [
...docs,
{
_index: '.internal.alerts-security.alerts-default-000002',
_id: 'd3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
found: true,
},
],
});
const clientArgs = { alertsService } as unknown as CasesClientArgs;

const res = await getAlerts(
[
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
},
],
clientArgs
);

expect(res).toEqual([
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
destination: { mac: 'ff:ff:ff:ff:ff:ff' },
source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 },
ecs: { version: '8.0.0' },
},
]);
});
});
10 changes: 9 additions & 1 deletion x-pack/plugins/cases/server/client/alerts/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
* 2.0.
*/

import { MgetResponseItem, GetGetResult } from '@elastic/elasticsearch/lib/api/types';
import { CasesClientGetAlertsResponse } from './types';
import { CasesClientArgs } from '..';
import { AlertInfo } from '../../common/types';
import { Alert } from '../../services/alerts';

function isAlert(
doc?: MgetResponseItem<unknown>
): doc is Omit<GetGetResult<Alert>, '_source'> & { _source: Alert } {
return Boolean(doc && !('error' in doc) && '_source' in doc);
}

export const getAlerts = async (
alertsInfo: AlertInfo[],
Expand All @@ -23,7 +31,7 @@ export const getAlerts = async (
return [];
}

return alerts.docs.map((alert) => ({
return alerts.docs.filter(isAlert).map((alert) => ({
id: alert._id,
index: alert._index,
...alert._source,
Expand Down
77 changes: 70 additions & 7 deletions x-pack/plugins/cases/server/services/alerts/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mo
describe('updateAlertsStatus', () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();
const logger = loggingSystemMock.create().get('case');
let alertService: AlertService;

describe('happy path', () => {
let alertService: AlertService;

beforeEach(async () => {
alertService = new AlertService(esClient, logger);
jest.resetAllMocks();
});
beforeEach(async () => {
alertService = new AlertService(esClient, logger);
jest.clearAllMocks();
});

describe('happy path', () => {
it('updates the status of the alert correctly', async () => {
const args = [{ id: 'alert-id-1', index: '.siem-signals', status: CaseStatuses.closed }];

Expand Down Expand Up @@ -273,4 +272,68 @@ describe('updateAlertsStatus', () => {
expect(esClient.updateByQuery).not.toHaveBeenCalled();
});
});

describe('getAlerts', () => {
const docs = [
{
_index: '.internal.alerts-security.alerts-default-000001',
_id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
_version: 2,
_seq_no: 255,
_primary_term: 1,
found: true,
_source: {
destination: { mac: 'ff:ff:ff:ff:ff:ff' },
source: { bytes: 444, mac: '11:1f:1e:13:15:14', packets: 6 },
ecs: { version: '8.0.0' },
},
},
];

esClient.mget.mockResolvedValue({ docs });

it('returns the alerts correctly', async () => {
const res = await alertService.getAlerts([
{
index: '.internal.alerts-security.alerts-default-000001',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
},
]);

expect(esClient.mget).toHaveBeenCalledWith({
body: {
docs: [
{
_id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
_index: '.internal.alerts-security.alerts-default-000001',
},
],
},
});

expect(res).toEqual({ docs });
});

it('returns undefined if the id is empty', async () => {
const res = await alertService.getAlerts([
{
index: '.internal.alerts-security.alerts-default-000001',
id: '',
},
]);

expect(res).toBe(undefined);
});

it('returns undefined if the index is empty', async () => {
const res = await alertService.getAlerts([
{
index: '',
id: 'c3869d546717e8c581add9cbf7d24578f34cd3e72cbc8d8b8e9a9330a899f70f',
},
]);

expect(res).toBe(undefined);
});
});
});
12 changes: 4 additions & 8 deletions x-pack/plugins/cases/server/services/alerts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ALERT_WORKFLOW_STATUS,
STATUS_VALUES,
} from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names';
import { MgetResponse } from '@elastic/elasticsearch/lib/api/types';
import { CaseStatuses } from '../../../common/api';
import { MAX_ALERTS_PER_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
import { createCaseError } from '../../common/error';
Expand Down Expand Up @@ -180,7 +181,7 @@ export class AlertService {
);
}

public async getAlerts(alertsInfo: AlertInfo[]): Promise<AlertsResponse | undefined> {
public async getAlerts(alertsInfo: AlertInfo[]): Promise<MgetResponse<Alert> | undefined> {
try {
const docs = alertsInfo
.filter((alert) => !AlertService.isEmptyAlert(alert))
Expand All @@ -193,8 +194,7 @@ export class AlertService {

const results = await this.scopedClusterClient.mget<Alert>({ body: { docs } });

// @ts-expect-error @elastic/elasticsearch _source is optional
return results.body;
return results;
} catch (error) {
throw createCaseError({
message: `Failed to retrieve alerts ids: ${JSON.stringify(alertsInfo)}: ${error}`,
Expand Down Expand Up @@ -230,16 +230,12 @@ function updateIndexEntryWithStatus(
}
}

interface Alert {
export interface Alert {
_id: string;
_index: string;
_source: Record<string, unknown>;
}

interface AlertsResponse {
docs: Alert[];
}

interface AlertIdIndex {
id: string;
index: string;
Expand Down

0 comments on commit 47ffdd8

Please sign in to comment.