Skip to content

Commit

Permalink
Exception List Telemetry (#107765)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjhampton authored Aug 20, 2021
1 parent fcd8970 commit 0f75573
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 152 deletions.
14 changes: 14 additions & 0 deletions x-pack/plugins/security_solution/server/lib/telemetry/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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.
*/

export const TELEMETRY_CHANNEL_LISTS = 'security-lists';

export const TELEMETRY_CHANNEL_ENDPOINT_META = 'endpoint-metadata';

export const LIST_ENDPOINT_EXCEPTION = 'endpoint_exception';

export const LIST_ENDPOINT_EVENT_FILTER = 'endpoint_event_filter';
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
EndpointPolicyResponseAggregation,
EndpointPolicyResponseDocument,
} from './types';
import { TELEMETRY_CHANNEL_ENDPOINT_META } from './constants';

export const TelemetryEndpointTaskConstants = {
TIMEOUT: '5m',
Expand Down Expand Up @@ -326,7 +327,7 @@ export class TelemetryEndpointTask {
* Send the documents in a batches of 100
*/
batchTelemetryRecords(telemetryPayloads, 100).forEach((telemetryBatch) =>
this.sender.sendOnDemand('endpoint-metadata', telemetryBatch)
this.sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_META, telemetryBatch)
);
return telemetryPayloads.length;
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@

import moment from 'moment';
import { createMockPackagePolicy } from './mocks';
import { TrustedApp } from '../../../common/endpoint/types';
import { LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER } from './constants';
import {
getPreviousDiagTaskTimestamp,
getPreviousEpMetaTaskTimestamp,
batchTelemetryRecords,
isPackagePolicyList,
templateTrustedApps,
templateEndpointExceptions,
} from './helpers';
import { EndpointExceptionListItem } from './types';

describe('test diagnostic telemetry scheduled task timing helper', () => {
test('test -5 mins is returned when there is no previous task run', async () => {
Expand Down Expand Up @@ -125,3 +130,67 @@ describe('test package policy type guard', () => {
expect(result).toEqual(true);
});
});

describe('list telemetry schema', () => {
test('trusted apps document is correctly formed', () => {
const data = [{ id: 'test_1' }] as TrustedApp[];
const templatedItems = templateTrustedApps(data);

expect(templatedItems[0]?.trusted_application.length).toEqual(1);
expect(templatedItems[0]?.endpoint_exception.length).toEqual(0);
expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0);
});

test('trusted apps document is correctly formed with multiple entries', () => {
const data = [{ id: 'test_2' }, { id: 'test_2' }] as TrustedApp[];
const templatedItems = templateTrustedApps(data);

expect(templatedItems[0]?.trusted_application.length).toEqual(1);
expect(templatedItems[1]?.trusted_application.length).toEqual(1);
expect(templatedItems[0]?.endpoint_exception.length).toEqual(0);
expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0);
});

test('endpoint exception document is correctly formed', () => {
const data = [{ id: 'test_3' }] as EndpointExceptionListItem[];
const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EXCEPTION);

expect(templatedItems[0]?.trusted_application.length).toEqual(0);
expect(templatedItems[0]?.endpoint_exception.length).toEqual(1);
expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0);
});

test('endpoint exception document is correctly formed with multiple entries', () => {
const data = [
{ id: 'test_4' },
{ id: 'test_4' },
{ id: 'test_4' },
] as EndpointExceptionListItem[];
const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EXCEPTION);

expect(templatedItems[0]?.trusted_application.length).toEqual(0);
expect(templatedItems[0]?.endpoint_exception.length).toEqual(1);
expect(templatedItems[1]?.endpoint_exception.length).toEqual(1);
expect(templatedItems[2]?.endpoint_exception.length).toEqual(1);
expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(0);
});

test('endpoint event filters document is correctly formed', () => {
const data = [{ id: 'test_5' }] as EndpointExceptionListItem[];
const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EVENT_FILTER);

expect(templatedItems[0]?.trusted_application.length).toEqual(0);
expect(templatedItems[0]?.endpoint_exception.length).toEqual(0);
expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(1);
});

test('endpoint event filters document is correctly formed with multiple entries', () => {
const data = [{ id: 'test_6' }, { id: 'test_6' }] as EndpointExceptionListItem[];
const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EVENT_FILTER);

expect(templatedItems[0]?.trusted_application.length).toEqual(0);
expect(templatedItems[0]?.endpoint_exception.length).toEqual(0);
expect(templatedItems[0]?.endpoint_event_filter.length).toEqual(1);
expect(templatedItems[1]?.endpoint_event_filter.length).toEqual(1);
});
});
79 changes: 78 additions & 1 deletion x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
*/

import moment from 'moment';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { TrustedApp } from '../../../common/endpoint/types';
import { PackagePolicy } from '../../../../fleet/common/types/models/package_policy';
import { EndpointExceptionListItem, ListTemplate } from './types';
import { LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER } from './constants';

/**
* Determines the when the last run was in order to execute to.
Expand Down Expand Up @@ -84,9 +88,82 @@ export function isPackagePolicyList(
return (data as PackagePolicy[])[0].inputs !== undefined;
}

/**
* Maps Exception list item to parsable object
*
* @param exceptionListItem
* @returns collection of endpoint exceptions
*/
export const exceptionListItemToEndpointEntry = (exceptionListItem: ExceptionListItemSchema) => {
return {
id: exceptionListItem.id,
version: exceptionListItem._version || '',
name: exceptionListItem.name,
description: exceptionListItem.description,
created_at: exceptionListItem.created_at,
created_by: exceptionListItem.created_by,
updated_at: exceptionListItem.updated_at,
updated_by: exceptionListItem.updated_by,
entries: exceptionListItem.entries,
os_types: exceptionListItem.os_types,
} as EndpointExceptionListItem;
};

/**
* Constructs the lists telemetry schema from a collection of Trusted Apps
*
* @param listData
* @returns lists telemetry schema
*/
export const templateTrustedApps = (listData: TrustedApp[]) => {
return listData.map((item) => {
const template: ListTemplate = {
trusted_application: [],
endpoint_exception: [],
endpoint_event_filter: [],
};

template.trusted_application.push(item);
return template;
});
};

/**
* Consructs the list telemetry schema from a collection of endpoint exceptions
*
* @param listData
* @param listType
* @returns lists telemetry schema
*/
export const templateEndpointExceptions = (
listData: EndpointExceptionListItem[],
listType: string
) => {
return listData.map((item) => {
const template: ListTemplate = {
trusted_application: [],
endpoint_exception: [],
endpoint_event_filter: [],
};

if (listType === LIST_ENDPOINT_EXCEPTION) {
template.endpoint_exception.push(item);
return template;
}

if (listType === LIST_ENDPOINT_EVENT_FILTER) {
template.endpoint_event_filter.push(item);
return template;
}

return null;
});
};

/**
* Convert counter label list to kebab case
* @params label_list the list of labels to create standardized UsageCounter from
*
* @param label_list the list of labels to create standardized UsageCounter from
* @returns a string label for usage in the UsageCounter
*/
export function createUsageCounterLabel(labelList: string[]): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { TelemetryEventsSender } from './sender';
import { TelemetryDiagTask } from './diagnostic_task';
import { TelemetryEndpointTask } from './endpoint_task';
import { TelemetryTrustedAppsTask } from './trusted_apps_task';
import { TelemetryExceptionListsTask } from './security_lists_task';
import { PackagePolicy } from '../../../../fleet/common/types/models/package_policy';

/**
Expand Down Expand Up @@ -69,8 +69,8 @@ export class MockTelemetryEndpointTask extends TelemetryEndpointTask {
}

/**
* Creates a mocked Telemetry trusted app Task
* Creates a mocked Telemetry exception lists Task
*/
export class MockTelemetryTrustedAppTask extends TelemetryTrustedAppsTask {
export class MockExceptionListsTask extends TelemetryExceptionListsTask {
public runTask = jest.fn();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,39 @@ import { loggingSystemMock } from 'src/core/server/mocks';
import { TaskStatus } from '../../../../task_manager/server';
import { taskManagerMock } from '../../../../task_manager/server/mocks';

import { TelemetryTrustedAppsTask, TelemetryTrustedAppsTaskConstants } from './trusted_apps_task';
import { createMockTelemetryEventsSender, MockTelemetryTrustedAppTask } from './mocks';
import {
TelemetryExceptionListsTask,
TelemetrySecuityListsTaskConstants,
} from './security_lists_task';
import { createMockTelemetryEventsSender, MockExceptionListsTask } from './mocks';

describe('test trusted apps telemetry task functionality', () => {
describe('test exception list telemetry task functionality', () => {
let logger: ReturnType<typeof loggingSystemMock.createLogger>;

beforeEach(() => {
logger = loggingSystemMock.createLogger();
});

test('the trusted apps task can register', () => {
const telemetryTrustedAppsTask = new TelemetryTrustedAppsTask(
const telemetryTrustedAppsTask = new TelemetryExceptionListsTask(
logger,
taskManagerMock.createSetup(),
createMockTelemetryEventsSender(true)
);

expect(telemetryTrustedAppsTask).toBeInstanceOf(TelemetryTrustedAppsTask);
expect(telemetryTrustedAppsTask).toBeInstanceOf(TelemetryExceptionListsTask);
});

test('the trusted apps task should be registered', () => {
test('the exception list task should be registered', () => {
const mockTaskManager = taskManagerMock.createSetup();
new TelemetryTrustedAppsTask(logger, mockTaskManager, createMockTelemetryEventsSender(true));
new TelemetryExceptionListsTask(logger, mockTaskManager, createMockTelemetryEventsSender(true));

expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalled();
});

test('the trusted apps task should be scheduled', async () => {
test('the exception list task should be scheduled', async () => {
const mockTaskManagerSetup = taskManagerMock.createSetup();
const telemetryTrustedAppsTask = new TelemetryTrustedAppsTask(
const telemetryTrustedAppsTask = new TelemetryExceptionListsTask(
logger,
mockTaskManagerSetup,
createMockTelemetryEventsSender(true)
Expand All @@ -49,13 +52,13 @@ describe('test trusted apps telemetry task functionality', () => {
expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled();
});

test('the trusted apps task should not query elastic if telemetry is not opted in', async () => {
test('the exception list task should not query elastic if telemetry is not opted in', async () => {
const mockSender = createMockTelemetryEventsSender(false);
const mockTaskManager = taskManagerMock.createSetup();
new MockTelemetryTrustedAppTask(logger, mockTaskManager, mockSender);
new MockExceptionListsTask(logger, mockTaskManager, mockSender);

const mockTaskInstance = {
id: TelemetryTrustedAppsTaskConstants.TYPE,
id: TelemetrySecuityListsTaskConstants.TYPE,
runAt: new Date(),
attempts: 0,
ownerId: '',
Expand All @@ -65,28 +68,28 @@ describe('test trusted apps telemetry task functionality', () => {
retryAt: new Date(),
params: {},
state: {},
taskType: TelemetryTrustedAppsTaskConstants.TYPE,
taskType: TelemetrySecuityListsTaskConstants.TYPE,
};
const createTaskRunner =
mockTaskManager.registerTaskDefinitions.mock.calls[0][0][
TelemetryTrustedAppsTaskConstants.TYPE
TelemetrySecuityListsTaskConstants.TYPE
].createTaskRunner;
const taskRunner = createTaskRunner({ taskInstance: mockTaskInstance });
await taskRunner.run();
expect(mockSender.fetchTrustedApplications).not.toHaveBeenCalled();
});

test('the trusted apps task should query elastic if telemetry opted in', async () => {
test('the exception list task should query elastic if telemetry opted in', async () => {
const mockSender = createMockTelemetryEventsSender(true);
const mockTaskManager = taskManagerMock.createSetup();
const telemetryTrustedAppsTask = new MockTelemetryTrustedAppTask(
const telemetryTrustedAppsTask = new MockExceptionListsTask(
logger,
mockTaskManager,
mockSender
);

const mockTaskInstance = {
id: TelemetryTrustedAppsTaskConstants.TYPE,
id: TelemetrySecuityListsTaskConstants.TYPE,
runAt: new Date(),
attempts: 0,
ownerId: '',
Expand All @@ -96,11 +99,11 @@ describe('test trusted apps telemetry task functionality', () => {
retryAt: new Date(),
params: {},
state: {},
taskType: TelemetryTrustedAppsTaskConstants.TYPE,
taskType: TelemetrySecuityListsTaskConstants.TYPE,
};
const createTaskRunner =
mockTaskManager.registerTaskDefinitions.mock.calls[0][0][
TelemetryTrustedAppsTaskConstants.TYPE
TelemetrySecuityListsTaskConstants.TYPE
].createTaskRunner;
const taskRunner = createTaskRunner({ taskInstance: mockTaskInstance });
await taskRunner.run();
Expand Down
Loading

0 comments on commit 0f75573

Please sign in to comment.