Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9de21e4
Diagnostic timelines task
szaffarano Nov 20, 2023
de6cfde
Fix test
szaffarano Nov 21, 2023
6e7259e
Merge remote-tracking branch 'upstream/main' into szaffarano/diagnost…
szaffarano Nov 21, 2023
e8da446
Add unit tests
szaffarano Nov 21, 2023
28ee063
Apply PR comments
szaffarano Nov 21, 2023
a8140c4
Merge remote-tracking branch 'upstream/main' into szaffarano/diagnost…
szaffarano Nov 21, 2023
54ac7c6
Apply PR comments
szaffarano Nov 21, 2023
66cf1c1
Fix validation
szaffarano Nov 21, 2023
b89d3c8
Fix validation
szaffarano Nov 21, 2023
f4ab900
Refactoring to reuse timelines diagnostic logic
szaffarano Nov 21, 2023
11c8e9a
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 21, 2023
0e7fa97
Fix test
szaffarano Nov 22, 2023
954a313
Apply PR comments
szaffarano Nov 22, 2023
80fd7e4
Merge remote-tracking branch 'upstream/main' into szaffarano/diagnost…
szaffarano Nov 22, 2023
b53a412
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 22, 2023
dd2d96a
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 22, 2023
8ca5c6d
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 22, 2023
49195a7
Merge branch 'main' into szaffarano/diagnostic-timelines
pjhampton Nov 22, 2023
0368056
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 22, 2023
7af160a
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 22, 2023
6d94ae9
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
7901401
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
93d5c48
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 23, 2023
f221de6
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
b595d48
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
6172705
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
a98a4cb
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
5786bee
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
e89e318
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
b7e9590
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
bc0c3a0
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
5def656
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 23, 2023
641adc2
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
84744a6
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
86a7051
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
2c7e13c
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
2b0be12
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
bada57f
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
51fcf81
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
cb5bb3c
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
6801b97
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 24, 2023
25d4ff8
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 27, 2023
538d43f
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 27, 2023
fc40696
Merge branch 'main' into szaffarano/diagnostic-timelines
szaffarano Nov 27, 2023
51b2569
Move constants to telemetry code
szaffarano Nov 27, 2023
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 @@ -9,7 +9,7 @@ import moment from 'moment';
import type { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
import { TaskStatus } from '@kbn/task-manager-plugin/server';
import type { TelemetryEventsSender } from '../sender';
import type { TelemetryReceiver } from '../receiver';
import type { ITelemetryReceiver, TelemetryReceiver } from '../receiver';
import type { SecurityTelemetryTaskConfig } from '../task';
import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/package_policy';
import { stubEndpointAlertResponse, stubProcessTree, stubFetchTimelineEvents } from './timeline';
Expand Down Expand Up @@ -71,7 +71,7 @@ export const stubLicenseInfo: ESLicense = {
export const createMockTelemetryReceiver = (
diagnosticsAlert?: unknown,
emptyTimelineTree?: boolean
): jest.Mocked<TelemetryReceiver> => {
): jest.Mocked<ITelemetryReceiver> => {
const processTreeResponse = emptyTimelineTree
? Promise.resolve([])
: Promise.resolve(Promise.resolve(stubProcessTree()));
Expand All @@ -91,12 +91,11 @@ export const createMockTelemetryReceiver = (
fetchEndpointList: jest.fn(),
fetchDetectionRules: jest.fn().mockReturnValue({ body: null }),
fetchEndpointMetadata: jest.fn(),
fetchTimelineEndpointAlerts: jest
.fn()
.mockReturnValue(Promise.resolve(stubEndpointAlertResponse())),
fetchTimelineAlerts: jest.fn().mockReturnValue(Promise.resolve(stubEndpointAlertResponse())),
buildProcessTree: jest.fn().mockReturnValue(processTreeResponse),
fetchTimelineEvents: jest.fn().mockReturnValue(Promise.resolve(stubFetchTimelineEvents())),
fetchValueListMetaData: jest.fn(),
getAlertsIndex: jest.fn().mockReturnValue('test-alerts-index'),
} as unknown as jest.Mocked<TelemetryReceiver>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const INSIGHTS_CHANNEL = 'security-insights-v1';

export const TASK_METRICS_CHANNEL = 'task-metrics';

export const DEFAULT_DIAGNOSTIC_INDEX = '.logs-endpoint.diagnostic.collection-*' as const;

export const DEFAULT_ADVANCED_POLICY_CONFIG_SETTINGS = {
linux: {
advanced: {
Expand Down
126 changes: 123 additions & 3 deletions x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,25 @@ import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/packag
import { merge, set } from 'lodash';
import type { Logger } from '@kbn/core/server';
import { sha256 } from 'js-sha256';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { copyAllowlistedFields, filterList } from './filterlists';
import type { PolicyConfig, PolicyData } from '../../../common/endpoint/types';
import type { PolicyConfig, PolicyData, SafeEndpointEvent } from '../../../common/endpoint/types';
import type { ITelemetryReceiver } from './receiver';
import type {
ExceptionListItem,
EnhancedAlertEvent,
ESClusterInfo,
ESLicense,
ExceptionListItem,
ExtraInfo,
ListTemplate,
TaskMetric,
TelemetryEvent,
TimeFrame,
TimelineResult,
TimelineTelemetryEvent,
ValueListResponse,
TaskMetric,
} from './types';
import type { TaskExecutionPeriod } from './task';
import {
LIST_DETECTION_RULE_EXCEPTION,
LIST_ENDPOINT_EXCEPTION,
Expand All @@ -30,6 +38,7 @@ import {
DEFAULT_ADVANCED_POLICY_CONFIG_SETTINGS,
} from './constants';
import { tagsToEffectScope } from '../../../common/endpoint/service/trusted_apps/mapping';
import { resolverEntity } from '../../endpoint/routes/resolver/entity/utils/build_resolver_entity';

/**
* Determines the when the last run was in order to execute to.
Expand Down Expand Up @@ -345,3 +354,114 @@ export const processK8sUsernames = (clusterId: string, event: TelemetryEvent): T

return event;
};

export const ranges = (
taskExecutionPeriod: TaskExecutionPeriod,
defaultIntervalInHours: number = 3
) => {
const rangeFrom = taskExecutionPeriod.last ?? `now-${defaultIntervalInHours}h`;
const rangeTo = taskExecutionPeriod.current;

return { rangeFrom, rangeTo };
};

export class TelemetryTimelineFetcher {
startTime: number;
private receiver: ITelemetryReceiver;
private extraInfo: Promise<ExtraInfo>;
private timeFrame: TimeFrame;

constructor(receiver: ITelemetryReceiver) {
this.receiver = receiver;
this.startTime = Date.now();
this.extraInfo = this.lookupExtraInfo();
this.timeFrame = this.calculateTimeFrame();
}

async fetchTimeline(event: estypes.SearchHit<EnhancedAlertEvent>): Promise<TimelineResult> {
const eventId = event._source ? event._source['event.id'] : 'unknown';
const alertUUID = event._source ? event._source['kibana.alert.uuid'] : 'unknown';

const entities = resolverEntity([event]);

// Build Tree
const tree = await this.receiver.buildProcessTree(
entities[0].id,
entities[0].schema,
this.timeFrame.startOfDay,
this.timeFrame.endOfDay
);

const nodeIds = Array.isArray(tree) ? tree.map((node) => node?.id.toString()) : [];

const eventsStore = await this.fetchEventLineage(nodeIds);

const telemetryTimeline: TimelineTelemetryEvent[] = Array.isArray(tree)
? tree.map((node) => {
return {
...node,
event: eventsStore.get(node.id.toString()),
};
})
: [];

let record;
if (telemetryTimeline.length >= 1) {
const { clusterInfo, licenseInfo } = await this.extraInfo;
record = {
'@timestamp': moment().toISOString(),
version: clusterInfo.version?.number,
cluster_name: clusterInfo.cluster_name,
cluster_uuid: clusterInfo.cluster_uuid,
license_uuid: licenseInfo?.uid,
alert_id: alertUUID,
event_id: eventId,
timeline: telemetryTimeline,
};
}

const result: TimelineResult = {
nodes: nodeIds.length,
events: eventsStore.size,
timeline: record,
};

return result;
}

private async fetchEventLineage(nodeIds: string[]): Promise<Map<string, SafeEndpointEvent>> {
const timelineEvents = await this.receiver.fetchTimelineEvents(nodeIds);
const eventsStore = new Map<string, SafeEndpointEvent>();
for (const event of timelineEvents.hits.hits) {
const doc = event._source;

if (doc !== null && doc !== undefined) {
const entityId = doc?.process?.entity_id?.toString();
if (entityId !== null && entityId !== undefined) eventsStore.set(entityId, doc);
}
}
return eventsStore;
}

private async lookupExtraInfo(): Promise<ExtraInfo> {
const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([
this.receiver.fetchClusterInfo(),
this.receiver.fetchLicenseInfo(),
]);

const clusterInfo: ESClusterInfo =
clusterInfoPromise.status === 'fulfilled' ? clusterInfoPromise.value : ({} as ESClusterInfo);

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

return { clusterInfo, licenseInfo };
}

private calculateTimeFrame(): TimeFrame {
const now = moment();
const startOfDay = now.startOf('day').toISOString();
const endOfDay = now.endOf('day').toISOString();
return { startOfDay, endOfDay };
}
}
25 changes: 18 additions & 7 deletions x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import type {
import { telemetryConfiguration } from './configuration';
import { ENDPOINT_METRICS_INDEX } from '../../../common/constants';
import { PREBUILT_RULES_PACKAGE_NAME } from '../../../common/detection_engine/constants';
import { DEFAULT_DIAGNOSTIC_INDEX } from './constants';

export interface ITelemetryReceiver {
start(
Expand Down Expand Up @@ -163,7 +164,11 @@ export interface ITelemetryReceiver {

fetchPrebuiltRuleAlerts(): Promise<{ events: TelemetryEvent[]; count: number }>;

fetchTimelineEndpointAlerts(interval: number): Promise<Array<SearchHit<EnhancedAlertEvent>>>;
fetchTimelineAlerts(
index: string,
rangeFrom: string,
rangeTo: string
): Promise<Array<SearchHit<EnhancedAlertEvent>>>;

buildProcessTree(
entityId: string,
Expand All @@ -177,6 +182,8 @@ export interface ITelemetryReceiver {
): Promise<SearchResponse<SafeEndpointEvent, Record<string, AggregationsAggregate>>>;

fetchValueListMetaData(interval: number): Promise<ValueListResponse>;

getAlertsIndex(): string | undefined;
}

export class TelemetryReceiver implements ITelemetryReceiver {
Expand Down Expand Up @@ -224,6 +231,10 @@ export class TelemetryReceiver implements ITelemetryReceiver {
return this.clusterInfo;
}

public getAlertsIndex(): string | undefined {
return this.alertsIndex;
}

public async fetchDetectionRulesPackageVersion(): Promise<Installation | undefined> {
return this.packageService?.asInternalUser.getInstallation(PREBUILT_RULES_PACKAGE_NAME);
}
Expand Down Expand Up @@ -396,7 +407,7 @@ export class TelemetryReceiver implements ITelemetryReceiver {

const query = {
expand_wildcards: ['open' as const, 'hidden' as const],
index: '.logs-endpoint.diagnostic.collection-*',
index: `${DEFAULT_DIAGNOSTIC_INDEX}-*`,
ignore_unavailable: true,
size: telemetryConfiguration.telemetry_max_buffer_size,
body: {
Expand Down Expand Up @@ -697,7 +708,7 @@ export class TelemetryReceiver implements ITelemetryReceiver {
return { events: telemetryEvents, count: aggregations?.prebuilt_rule_alert_count.value ?? 0 };
}

public async fetchTimelineEndpointAlerts(interval: number) {
async fetchTimelineAlerts(index: string, rangeFrom: string, rangeTo: string) {
if (this.esClient === undefined || this.esClient === null) {
throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation');
}
Expand All @@ -708,7 +719,7 @@ export class TelemetryReceiver implements ITelemetryReceiver {
// create and assign an initial point in time
let pitId: OpenPointInTimeResponse['id'] = (
await this.esClient.openPointInTime({
index: `${this.alertsIndex}*`,
index: `${index}*`,
keep_alive: keepAlive,
})
).id;
Expand Down Expand Up @@ -746,8 +757,8 @@ export class TelemetryReceiver implements ITelemetryReceiver {
{
range: {
'@timestamp': {
gte: `now-${interval}h`,
lte: 'now',
gte: rangeFrom,
lte: rangeTo,
},
},
},
Expand Down Expand Up @@ -807,7 +818,7 @@ export class TelemetryReceiver implements ITelemetryReceiver {
}

tlog(this.logger, `Timeline alerts to return: ${alertsToReturn.length}`);
return alertsToReturn;
return alertsToReturn || [];
}

public async buildProcessTree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n
) => {
const startTime = Date.now();
const taskName = 'Security Solution Detection Rule Lists Telemetry';

tlog(
logger,
`Running task: ${taskId} [last: ${taskExecutionPeriod.last} - current: ${taskExecutionPeriod.current}]`
);

try {
const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([
receiver.fetchClusterInfo(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createTelemetrySecurityListTaskConfig } from './security_lists';
import { createTelemetryDetectionRuleListsTaskConfig } from './detection_rule';
import { createTelemetryPrebuiltRuleAlertsTaskConfig } from './prebuilt_rule_alerts';
import { createTelemetryTimelineTaskConfig } from './timelines';
import { createTelemetryDiagnosticTimelineTaskConfig } from './timelines_diagnostic';
import { createTelemetryConfigurationTaskConfig } from './configuration';
import { telemetryConfiguration } from '../configuration';
import { createTelemetryFilterListArtifactTaskConfig } from './filterlists';
Expand All @@ -26,6 +27,7 @@ export function createTelemetryTaskConfigs(): SecurityTelemetryTaskConfig[] {
),
createTelemetryPrebuiltRuleAlertsTaskConfig(telemetryConfiguration.max_detection_alerts_batch),
createTelemetryTimelineTaskConfig(),
createTelemetryDiagnosticTimelineTaskConfig(),
createTelemetryConfigurationTaskConfig(),
createTelemetryFilterListArtifactTaskConfig(),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ describe('security telemetry - ', () => {
expect(taskConfig.interval).toEqual('24h');
});

test('timelines task is set to 3h', async () => {
test('timelines task is set to 1h', async () => {
const taskConfig = createTelemetryTimelineTaskConfig();
expect(taskConfig.interval).toEqual('3h');
expect(taskConfig.interval).toEqual('1h');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('timeline telemetry task test', () => {

expect(mockTelemetryReceiver.buildProcessTree).toHaveBeenCalled();
expect(mockTelemetryReceiver.fetchTimelineEvents).toHaveBeenCalled();
expect(mockTelemetryReceiver.fetchTimelineEndpointAlerts).toHaveBeenCalled();
expect(mockTelemetryReceiver.fetchTimelineAlerts).toHaveBeenCalled();
expect(mockTelemetryEventsSender.getTelemetryUsageCluster).toHaveBeenCalled();
expect(mockTelemetryEventsSender.sendOnDemand).toHaveBeenCalled();
});
Expand All @@ -59,6 +59,6 @@ describe('timeline telemetry task test', () => {

expect(mockTelemetryReceiver.buildProcessTree).toHaveBeenCalled();
expect(mockTelemetryReceiver.fetchTimelineEvents).toHaveBeenCalled();
expect(mockTelemetryReceiver.fetchTimelineEndpointAlerts).toHaveBeenCalled();
expect(mockTelemetryReceiver.fetchTimelineAlerts).toHaveBeenCalled();
});
});
Loading