Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cluster name to EP/Lists telemetry #122429

Merged
merged 7 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ import { TelemetryReceiver } from '../receiver';
import { SecurityTelemetryTaskConfig } from '../task';
import { PackagePolicy } from '../../../../../fleet/common/types/models/package_policy';

/**
* Creates a mocked Telemetry Events Sender
*/
export const createMockTelemetryEventsSender = (
enableTelemetry?: boolean
): jest.Mocked<TelemetryEventsSender> => {
Expand All @@ -37,6 +34,7 @@ export const createMockTelemetryReceiver = (
): jest.Mocked<TelemetryReceiver> => {
return {
start: jest.fn(),
fetchClusterInfo: jest.fn(),
fetchLicenseInfo: jest.fn(),
copyLicenseFields: jest.fn(),
fetchFleetAgents: jest.fn(),
Expand All @@ -49,9 +47,6 @@ export const createMockTelemetryReceiver = (
} as unknown as jest.Mocked<TelemetryReceiver>;
};

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, superfluous, good to remove

* Creates a mocked package policy
*/
export const createMockPackagePolicy = (): jest.Mocked<PackagePolicy> => {
return {
id: jest.fn(),
Expand All @@ -65,9 +60,6 @@ export const createMockPackagePolicy = (): jest.Mocked<PackagePolicy> => {
} as unknown as jest.Mocked<PackagePolicy>;
};

/**
* Creates a mocked Security Telemetry Task Config
*/
export const createMockSecurityTelemetryTask = (
testType?: string,
testLastTimestamp?: string
Expand All @@ -83,9 +75,6 @@ export const createMockSecurityTelemetryTask = (
} as unknown as jest.Mocked<SecurityTelemetryTaskConfig>;
};

/**
* Creates a mocked Task Instance
*/
export const createMockTaskInstance = (testId: string, testType: string): ConcreteTaskInstance => {
return {
id: testId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
isPackagePolicyList,
templateExceptionList,
} from './helpers';
import { ExceptionListItem } from './types';
import { ESClusterInfo, ESLicense, ExceptionListItem } 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 @@ -135,9 +135,20 @@ describe('test package policy type guard', () => {
});

describe('list telemetry schema', () => {
const clusterInfo = {
cluster_uuid: 'stub_cluster',
cluster_name: 'stub_cluster',
} as ESClusterInfo;
const licenseInfo = { uid: 'stub_license' } as ESLicense;

test('detection rules document is correctly formed', () => {
const data = [{ id: 'test_1' }] as ExceptionListItem[];
const templatedItems = templateExceptionList(data, LIST_DETECTION_RULE_EXCEPTION);
const templatedItems = templateExceptionList(
data,
clusterInfo,
licenseInfo,
LIST_DETECTION_RULE_EXCEPTION
);

expect(templatedItems[0]?.detection_rule).not.toBeUndefined();
expect(templatedItems[0]?.endpoint_exception).toBeUndefined();
Expand All @@ -147,7 +158,12 @@ describe('list telemetry schema', () => {

test('detection rules document is correctly formed with multiple entries', () => {
const data = [{ id: 'test_2' }, { id: 'test_2' }] as ExceptionListItem[];
const templatedItems = templateExceptionList(data, LIST_DETECTION_RULE_EXCEPTION);
const templatedItems = templateExceptionList(
data,
clusterInfo,
licenseInfo,
LIST_DETECTION_RULE_EXCEPTION
);

expect(templatedItems[0]?.detection_rule).not.toBeUndefined();
expect(templatedItems[1]?.detection_rule).not.toBeUndefined();
Expand All @@ -158,7 +174,12 @@ describe('list telemetry schema', () => {

test('trusted apps document is correctly formed', () => {
const data = [{ id: 'test_1' }] as ExceptionListItem[];
const templatedItems = templateExceptionList(data, LIST_TRUSTED_APPLICATION);
const templatedItems = templateExceptionList(
data,
clusterInfo,
licenseInfo,
LIST_TRUSTED_APPLICATION
);

expect(templatedItems[0]?.detection_rule).toBeUndefined();
expect(templatedItems[0]?.endpoint_exception).toBeUndefined();
Expand All @@ -168,7 +189,12 @@ describe('list telemetry schema', () => {

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

expect(templatedItems[0]?.detection_rule).toBeUndefined();
expect(templatedItems[0]?.endpoint_exception).toBeUndefined();
Expand All @@ -179,7 +205,12 @@ describe('list telemetry schema', () => {

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

expect(templatedItems[0]?.detection_rule).toBeUndefined();
expect(templatedItems[0]?.endpoint_exception).not.toBeUndefined();
Expand All @@ -189,7 +220,12 @@ describe('list telemetry schema', () => {

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

expect(templatedItems[0]?.detection_rule).toBeUndefined();
expect(templatedItems[0]?.endpoint_event_filter).toBeUndefined();
Expand All @@ -201,7 +237,12 @@ describe('list telemetry schema', () => {

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

expect(templatedItems[0]?.detection_rule).toBeUndefined();
expect(templatedItems[0]?.endpoint_event_filter).not.toBeUndefined();
Expand All @@ -211,7 +252,12 @@ describe('list telemetry schema', () => {

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

expect(templatedItems[0]?.detection_rule).toBeUndefined();
expect(templatedItems[0]?.endpoint_event_filter).not.toBeUndefined();
Expand Down
12 changes: 10 additions & 2 deletions x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import moment from 'moment';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { PackagePolicy } from '../../../../fleet/common/types/models/package_policy';
import { copyAllowlistedFields, exceptionListEventFields } from './filters';
import { ExceptionListItem, ListTemplate, TelemetryEvent } from './types';
import { ExceptionListItem, ESClusterInfo, ESLicense, ListTemplate, TelemetryEvent } from './types';
import {
LIST_DETECTION_RULE_EXCEPTION,
LIST_ENDPOINT_EXCEPTION,
Expand Down Expand Up @@ -160,10 +160,18 @@ export const ruleExceptionListItemToTelemetryEvent = (
* @param listType
* @returns lists telemetry schema
*/
export const templateExceptionList = (listData: ExceptionListItem[], listType: string) => {
export const templateExceptionList = (
listData: ExceptionListItem[],
clusterInfo: ESClusterInfo,
licenseInfo: ESLicense | undefined,
listType: string
) => {
return listData.map((item) => {
const template: ListTemplate = {
'@timestamp': moment().toISOString(),
cluster_uuid: clusterInfo.cluster_uuid,
cluster_name: clusterInfo.cluster_name,
license_id: licenseInfo?.uid,
};

// cast exception list type to a TelemetryEvent for allowlist filtering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export class TelemetryReceiver {
};
}

private async fetchClusterInfo(): Promise<ESClusterInfo> {
public async fetchClusterInfo(): Promise<ESClusterInfo> {
if (this.esClient === undefined || this.esClient === null) {
throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export class TelemetryEventsSender {
telemetryUrl,
'alerts-endpoint',
clusterInfo?.cluster_uuid,
clusterInfo?.cluster_name,
clusterInfo?.version?.number,
licenseInfo?.uid
);
Expand Down Expand Up @@ -220,6 +221,7 @@ export class TelemetryEventsSender {
telemetryUrl,
channel,
clusterInfo?.cluster_uuid,
clusterInfo?.cluster_name,
clusterInfo?.version?.number,
licenseInfo?.uid
);
Expand Down Expand Up @@ -254,6 +256,7 @@ export class TelemetryEventsSender {
telemetryUrl: string,
channel: string,
clusterUuid: string | undefined,
clusterName: string | undefined,
clusterVersionNumber: string | undefined,
licenseId: string | undefined
) {
Expand All @@ -265,6 +268,7 @@ export class TelemetryEventsSender {
headers: {
'Content-Type': 'application/x-ndjson',
'X-Elastic-Cluster-ID': clusterUuid,
'X-Elastic-Cluster-Name': clusterName,
'X-Elastic-Stack-Version': clusterVersionNumber ? clusterVersionNumber : '8.0.0',
...(licenseId ? { 'X-Elastic-License-ID': licenseId } : {}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { LIST_DETECTION_RULE_EXCEPTION, TELEMETRY_CHANNEL_LISTS } from '../const
import { batchTelemetryRecords, templateExceptionList } from '../helpers';
import { TelemetryEventsSender } from '../sender';
import { TelemetryReceiver } from '../receiver';
import { ExceptionListItem, RuleSearchResult } from '../types';
import { ExceptionListItem, ESClusterInfo, ESLicense, RuleSearchResult } from '../types';
import { TaskExecutionPeriod } from '../task';

export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: number) {
Expand All @@ -27,6 +27,20 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n
sender: TelemetryEventsSender,
taskExecutionPeriod: TaskExecutionPeriod
) => {
const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([
receiver.fetchClusterInfo(),
receiver.fetchLicenseInfo(),
]);

const clusterInfo =
clusterInfoPromise.status === 'fulfilled'
? clusterInfoPromise.value
: ({} as ESClusterInfo);
const licenseInfo =
licenseInfoPromise.status === 'fulfilled'
? licenseInfoPromise.value
: ({} as ESLicense | undefined);

// Lists Telemetry: Detection Rules

const { body: prebuiltRules } = await receiver.fetchDetectionRules();
Expand Down Expand Up @@ -69,6 +83,8 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n

const detectionRuleExceptionsJson = templateExceptionList(
detectionRuleExceptions,
clusterInfo,
licenseInfo,
LIST_DETECTION_RULE_EXCEPTION
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
EndpointMetricsAggregation,
EndpointPolicyResponseAggregation,
EndpointPolicyResponseDocument,
ESClusterInfo,
ESLicense,
} from '../types';
import { TelemetryReceiver } from '../receiver';
import { TaskExecutionPeriod } from '../task';
Expand Down Expand Up @@ -52,6 +54,20 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) {
throw new Error('last execution timestamp is required');
}

const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([
receiver.fetchClusterInfo(),
receiver.fetchLicenseInfo(),
]);

const clusterInfo =
clusterInfoPromise.status === 'fulfilled'
? clusterInfoPromise.value
: ({} as ESClusterInfo);
const licenseInfo =
licenseInfoPromise.status === 'fulfilled'
? licenseInfoPromise.value
: ({} as ESLicense | undefined);

const endpointData = await fetchEndpointData(
receiver,
taskExecutionPeriod.last,
Expand Down Expand Up @@ -199,6 +215,9 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) {

return {
'@timestamp': taskExecutionPeriod.current,
cluster_uuid: clusterInfo.cluster_uuid,
cluster_name: clusterInfo.cluster_name,
license_id: licenseInfo?.uid,
endpoint_id: endpointAgentId,
endpoint_version: endpoint.endpoint_version,
endpoint_package_version: policyConfig?.package?.version || null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
LIST_TRUSTED_APPLICATION,
TELEMETRY_CHANNEL_LISTS,
} from '../constants';
import { ESClusterInfo, ESLicense } from '../types';
import { batchTelemetryRecords, templateExceptionList } from '../helpers';
import { TelemetryEventsSender } from '../sender';
import { TelemetryReceiver } from '../receiver';
Expand All @@ -37,11 +38,30 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number)
) => {
let count = 0;

const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([
receiver.fetchClusterInfo(),
receiver.fetchLicenseInfo(),
]);

const clusterInfo =
clusterInfoPromise.status === 'fulfilled'
? clusterInfoPromise.value
: ({} as ESClusterInfo);
const licenseInfo =
licenseInfoPromise.status === 'fulfilled'
? licenseInfoPromise.value
: ({} as ESLicense | undefined);

// Lists Telemetry: Trusted Applications

const trustedApps = await receiver.fetchTrustedApplications();
if (trustedApps?.data) {
const trustedAppsJson = templateExceptionList(trustedApps.data, LIST_TRUSTED_APPLICATION);
const trustedAppsJson = templateExceptionList(
trustedApps.data,
clusterInfo,
licenseInfo,
LIST_TRUSTED_APPLICATION
);
logger.debug(`Trusted Apps: ${trustedAppsJson}`);
count += trustedAppsJson.length;

Expand All @@ -54,7 +74,12 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number)

const epExceptions = await receiver.fetchEndpointList(ENDPOINT_LIST_ID);
if (epExceptions?.data) {
const epExceptionsJson = templateExceptionList(epExceptions.data, LIST_ENDPOINT_EXCEPTION);
const epExceptionsJson = templateExceptionList(
epExceptions.data,
clusterInfo,
licenseInfo,
LIST_ENDPOINT_EXCEPTION
);
logger.debug(`EP Exceptions: ${epExceptionsJson}`);
count += epExceptionsJson.length;

Expand All @@ -67,7 +92,12 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number)

const epFilters = await receiver.fetchEndpointList(ENDPOINT_EVENT_FILTERS_LIST_ID);
if (epFilters?.data) {
const epFiltersJson = templateExceptionList(epFilters.data, LIST_ENDPOINT_EVENT_FILTER);
const epFiltersJson = templateExceptionList(
epFilters.data,
clusterInfo,
licenseInfo,
LIST_ENDPOINT_EVENT_FILTER
);
logger.debug(`EP Event Filters: ${epFiltersJson}`);
count += epFiltersJson.length;

Expand Down
Loading