[Security Solution] Cleanup endpoint telemetry (#71950)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
96d965d4e3
commit
2094f33537
5 changed files with 478 additions and 167 deletions
|
@ -12,7 +12,7 @@ import { EndpointUsage, getEndpointTelemetryFromFleet } from './endpoints';
|
|||
export type RegisterCollector = (deps: CollectorDependencies) => void;
|
||||
export interface UsageData {
|
||||
detections: DetectionsUsage;
|
||||
endpoints: EndpointUsage;
|
||||
endpoints: EndpointUsage | {};
|
||||
}
|
||||
|
||||
export async function getInternalSavedObjectsClient(core: CoreSetup) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import { FLEET_ENDPOINT_PACKAGE_CONSTANT } from './fleet_saved_objects';
|
|||
|
||||
const testAgentId = 'testAgentId';
|
||||
const testConfigId = 'testConfigId';
|
||||
const testHostId = 'randoHostId';
|
||||
|
||||
/** Mock OS Platform for endpoint telemetry */
|
||||
export const MockOSPlatform = 'somePlatform';
|
||||
|
@ -30,6 +31,7 @@ export const MockOSFullName = 'somePlatformFullName';
|
|||
* @description We request the install and OS related telemetry information from the 'fleet-agents' saved objects in ingest_manager. This mocks that response
|
||||
*/
|
||||
export const mockFleetObjectsResponse = (
|
||||
hasDuplicates = true,
|
||||
lastCheckIn = new Date().toISOString()
|
||||
): SavedObjectsFindResponse<Agent> => ({
|
||||
page: 1,
|
||||
|
@ -56,7 +58,44 @@ export const mockFleetObjectsResponse = (
|
|||
host: {
|
||||
hostname: 'testDesktop',
|
||||
name: 'testDesktop',
|
||||
id: 'randoHostId',
|
||||
id: testHostId,
|
||||
},
|
||||
os: {
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
name: MockOSName,
|
||||
full: MockOSFullName,
|
||||
},
|
||||
},
|
||||
packages: [FLEET_ENDPOINT_PACKAGE_CONSTANT, 'system'],
|
||||
last_checkin: lastCheckIn,
|
||||
},
|
||||
references: [],
|
||||
updated_at: lastCheckIn,
|
||||
version: 'WzI4MSwxXQ==',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
id: testAgentId,
|
||||
attributes: {
|
||||
active: true,
|
||||
id: 'oldTestAgentId',
|
||||
config_id: 'randoConfigId',
|
||||
type: 'PERMANENT',
|
||||
user_provided_metadata: {},
|
||||
enrolled_at: lastCheckIn,
|
||||
current_error_events: [],
|
||||
local_metadata: {
|
||||
elastic: {
|
||||
agent: {
|
||||
id: 'oldTestAgentId',
|
||||
},
|
||||
},
|
||||
host: {
|
||||
hostname: 'testDesktop',
|
||||
name: 'testDesktop',
|
||||
id: hasDuplicates ? testHostId : 'oldRandoHostId',
|
||||
},
|
||||
os: {
|
||||
platform: MockOSPlatform,
|
||||
|
@ -76,7 +115,10 @@ export const mockFleetObjectsResponse = (
|
|||
],
|
||||
});
|
||||
|
||||
const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') =>
|
||||
const mockPolicyPayload = (
|
||||
policyStatus: 'success' | 'warning' | 'failure',
|
||||
policyMode: 'prevent' | 'detect' | 'off' = 'prevent'
|
||||
) =>
|
||||
JSON.stringify({
|
||||
'endpoint-security': {
|
||||
Endpoint: {
|
||||
|
@ -105,7 +147,7 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') =>
|
|||
file: 'info',
|
||||
},
|
||||
malware: {
|
||||
mode: 'prevent',
|
||||
mode: policyMode,
|
||||
},
|
||||
},
|
||||
windows: {
|
||||
|
@ -122,7 +164,7 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') =>
|
|||
file: 'info',
|
||||
},
|
||||
malware: {
|
||||
mode: 'prevent',
|
||||
mode: policyMode,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -151,11 +193,11 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') =>
|
|||
'detect_file_open_events',
|
||||
'detect_sync_image_load_events',
|
||||
],
|
||||
status: `${malwareStatus}`,
|
||||
status: `${policyStatus}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
status: `${malwareStatus}`,
|
||||
status: `${policyStatus}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -186,7 +228,9 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') =>
|
|||
*/
|
||||
export const mockFleetEventsObjectsResponse = (
|
||||
running?: boolean,
|
||||
updatedDate = new Date().toISOString()
|
||||
updatedDate = new Date().toISOString(),
|
||||
policyStatus: 'success' | 'failure' = running ? 'success' : 'failure',
|
||||
policyMode: 'prevent' | 'detect' | 'off' = 'prevent'
|
||||
): SavedObjectsFindResponse<AgentEventSOAttributes> => {
|
||||
return {
|
||||
page: 1,
|
||||
|
@ -204,7 +248,7 @@ export const mockFleetEventsObjectsResponse = (
|
|||
message: `Application: endpoint-security--8.0.0[d8f7f6e8-9375-483c-b456-b479f1d7a4f2]: State changed to ${
|
||||
running ? 'RUNNING' : 'FAILED'
|
||||
}: `,
|
||||
payload: mockPolicyPayload(running ? 'success' : 'failure'),
|
||||
payload: running ? mockPolicyPayload(policyStatus, policyMode) : undefined,
|
||||
config_id: testConfigId,
|
||||
},
|
||||
references: [],
|
||||
|
|
|
@ -51,17 +51,31 @@ describe('test security solution endpoint telemetry', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
describe('when a request for endpoint agents fails', () => {
|
||||
it('should return an empty object', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.reject(Error('No agents for you'))
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(getFleetSavedObjectsMetadataSpy).toHaveBeenCalled();
|
||||
expect(endpointUsage).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when an agent has not been installed', () => {
|
||||
it('should return the default shape if no agents are found', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve({ saved_objects: [], total: 0, per_page: 0, page: 0 })
|
||||
);
|
||||
|
||||
const emptyEndpointTelemetryData = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(getFleetSavedObjectsMetadataSpy).toHaveBeenCalled();
|
||||
expect(emptyEndpointTelemetryData).toEqual({
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 0,
|
||||
active_within_last_24_hours: 0,
|
||||
os: [],
|
||||
|
@ -76,68 +90,274 @@ describe('test security solution endpoint telemetry', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when an agent has been installed', () => {
|
||||
it('should show one enpoint installed but it is inactive', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetEventsObjectsResponse())
|
||||
);
|
||||
describe('when agent(s) have been installed', () => {
|
||||
describe('when a request for events has failed', () => {
|
||||
it('should show only one endpoint installed but it is inactive', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.reject(Error('No events for you'))
|
||||
);
|
||||
|
||||
const emptyEndpointTelemetryData = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(emptyEndpointTelemetryData).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 0,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 0,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 1,
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show one endpoint installed and it is active', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetEventsObjectsResponse(true))
|
||||
);
|
||||
describe('when a request for events is successful', () => {
|
||||
it('should show one endpoint installed but endpoint has failed to run', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetEventsObjectsResponse())
|
||||
);
|
||||
|
||||
const emptyEndpointTelemetryData = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(emptyEndpointTelemetryData).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 1,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 0,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 1,
|
||||
inactive: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should show two endpoints installed but both endpoints have failed to run', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse(false))
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetEventsObjectsResponse())
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 2,
|
||||
active_within_last_24_hours: 0,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should show two endpoints installed but agents have not checked in within past day', async () => {
|
||||
const twoDaysAgo = new Date();
|
||||
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
|
||||
const twoDaysAgoISOString = twoDaysAgo.toISOString();
|
||||
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse(false, twoDaysAgoISOString))
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(
|
||||
() => Promise.resolve(mockFleetEventsObjectsResponse(true, twoDaysAgoISOString)) // agent_id doesn't matter for mock here
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 2,
|
||||
active_within_last_24_hours: 0,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 2,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should show one endpoint installed and endpoint is running', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetEventsObjectsResponse(true))
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 1,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 1,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('malware policy', () => {
|
||||
it('should have failed to enable', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
mockFleetEventsObjectsResponse(true, new Date().toISOString(), 'failure')
|
||||
)
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 1,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 1,
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should be enabled successfully', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetEventsObjectsResponse(true))
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 1,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 1,
|
||||
inactive: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should be disabled successfully', async () => {
|
||||
getFleetSavedObjectsMetadataSpy.mockImplementation(() =>
|
||||
Promise.resolve(mockFleetObjectsResponse())
|
||||
);
|
||||
getLatestFleetEndpointEventSpy.mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
mockFleetEventsObjectsResponse(true, new Date().toISOString(), 'success', 'off')
|
||||
)
|
||||
);
|
||||
|
||||
const endpointUsage = await endpointTelemetry.getEndpointTelemetryFromFleet(
|
||||
mockSavedObjectsRepository
|
||||
);
|
||||
expect(endpointUsage).toEqual({
|
||||
total_installed: 1,
|
||||
active_within_last_24_hours: 1,
|
||||
os: [
|
||||
{
|
||||
full_name: MockOSFullName,
|
||||
platform: MockOSPlatform,
|
||||
version: MockOSVersion,
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
policies: {
|
||||
malware: {
|
||||
failure: 0,
|
||||
active: 0,
|
||||
inactive: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,8 +16,16 @@ export const FLEET_ENDPOINT_PACKAGE_CONSTANT = FleetDefaultPackages.endpoint;
|
|||
|
||||
export const getFleetSavedObjectsMetadata = async (savedObjectsClient: ISavedObjectsRepository) =>
|
||||
savedObjectsClient.find<Agent>({
|
||||
// Get up to 10000 agents with endpoint installed
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
fields: ['packages', 'last_checkin', 'local_metadata'],
|
||||
fields: [
|
||||
'packages',
|
||||
'last_checkin',
|
||||
'local_metadata.agent.id',
|
||||
'local_metadata.host.id',
|
||||
'local_metadata.elastic.agent.id',
|
||||
'local_metadata.os',
|
||||
],
|
||||
filter: `${AGENT_SAVED_OBJECT_TYPE}.attributes.packages: ${FLEET_ENDPOINT_PACKAGE_CONSTANT}`,
|
||||
perPage: 10000,
|
||||
sortField: 'enrolled_at',
|
||||
|
@ -29,9 +37,11 @@ export const getLatestFleetEndpointEvent = async (
|
|||
agentId: string
|
||||
) =>
|
||||
savedObjectsClient.find<AgentEventSOAttributes>({
|
||||
// Get the most recent endpoint event.
|
||||
type: AGENT_EVENT_SAVED_OBJECT_TYPE,
|
||||
filter: `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.agent_id: ${agentId} and ${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.message: "${FLEET_ENDPOINT_PACKAGE_CONSTANT}"`,
|
||||
perPage: 1, // Get the most recent endpoint event.
|
||||
fields: ['agent_id', 'subtype', 'payload'],
|
||||
filter: `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.message: "${FLEET_ENDPOINT_PACKAGE_CONSTANT}"`,
|
||||
perPage: 1,
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
search: agentId,
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { ISavedObjectsRepository } from 'src/core/server';
|
||||
import { SavedObject } from './../../../../../../src/core/types/saved_objects';
|
||||
import { Agent, NewAgentEvent } from './../../../../ingest_manager/common/types/models/agent';
|
||||
import { AgentMetadata } from '../../../../ingest_manager/common/types/models/agent';
|
||||
import { getFleetSavedObjectsMetadata, getLatestFleetEndpointEvent } from './fleet_saved_objects';
|
||||
|
||||
|
@ -51,7 +53,7 @@ export interface AgentLocalMetadata extends AgentMetadata {
|
|||
}
|
||||
|
||||
type OSTracker = Record<string, AgentOSMetadataTelemetry>;
|
||||
type AgentDailyActiveTracker = Map<string, boolean>;
|
||||
|
||||
/**
|
||||
* @description returns an empty telemetry object to be incrmented and updated within the `getEndpointTelemetryFromFleet` fn
|
||||
*/
|
||||
|
@ -69,13 +71,14 @@ export const getDefaultEndpointTelemetry = (): EndpointUsage => ({
|
|||
});
|
||||
|
||||
/**
|
||||
* @description this fun
|
||||
* @description this function updates the os telemetry. We use the fullName field as the key as it contains the name and version details.
|
||||
* If it has already been tracked, the count will be updated, otherwise a tracker will be initialized for that fullName.
|
||||
*/
|
||||
export const trackEndpointOSTelemetry = (
|
||||
export const updateEndpointOSTelemetry = (
|
||||
os: AgentLocalMetadata['os'],
|
||||
osTracker: OSTracker
|
||||
): OSTracker => {
|
||||
const updatedOSTracker = { ...osTracker };
|
||||
const updatedOSTracker = cloneDeep(osTracker);
|
||||
const { version: osVersion, platform: osPlatform, full: osFullName } = os;
|
||||
if (osFullName && osVersion) {
|
||||
if (updatedOSTracker[osFullName]) updatedOSTracker[osFullName].count += 1;
|
||||
|
@ -93,18 +96,32 @@ export const trackEndpointOSTelemetry = (
|
|||
};
|
||||
|
||||
/**
|
||||
* @description This iterates over all unique agents that currently track an endpoint package. It takes a list of agents who have checked in in the last 24 hours
|
||||
* and then checks whether those agents have endpoints whose latest status is 'RUNNING' to determine an active_within_last_24_hours. Since the policy information is also tracked in these events
|
||||
* we pull out the status of the current protection (malware) type. This must be done in a compound manner as the desired status is reflected in the config, and the successful application of that policy
|
||||
* is tracked in the policy.applied.response.configurations[protectionsType].status. Using these two we can determine whether the policy is toggled on, off, or failed to turn on.
|
||||
* @description we take the latest endpoint specific agent event, get the status of the endpoint, and if it is running
|
||||
* and the agent itself has been active within the last 24 hours, we can safely assume the endpoint has been active within
|
||||
* the same time span.
|
||||
*/
|
||||
export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async (
|
||||
agentDailyActiveTracker: AgentDailyActiveTracker,
|
||||
savedObjectsClient: ISavedObjectsRepository,
|
||||
endpointTelemetry: EndpointUsage
|
||||
): Promise<EndpointUsage> => {
|
||||
const updatedEndpointTelemetry = { ...endpointTelemetry };
|
||||
export const updateEndpointDailyActiveCount = (
|
||||
latestEndpointEvent: SavedObject<NewAgentEvent>,
|
||||
lastAgentCheckin: Agent['last_checkin'],
|
||||
currentCount: number
|
||||
) => {
|
||||
const aDayAgo = new Date();
|
||||
aDayAgo.setDate(aDayAgo.getDate() - 1);
|
||||
|
||||
const agentWasActiveOverLastDay = !!lastAgentCheckin && new Date(lastAgentCheckin) > aDayAgo;
|
||||
return agentWasActiveOverLastDay && latestEndpointEvent.attributes.subtype === 'RUNNING'
|
||||
? currentCount + 1
|
||||
: currentCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description We take the latest endpoint specific agent event, and as long as it provides the payload with policy details, we will parse that policy
|
||||
* to populate the success of it's application. The policy is provided in the agent health checks.
|
||||
*/
|
||||
export const updateEndpointPolicyTelemetry = (
|
||||
latestEndpointEvent: SavedObject<NewAgentEvent>,
|
||||
policiesTracker: PoliciesTelemetry
|
||||
): PoliciesTelemetry => {
|
||||
const policyHostTypeToPolicyType = {
|
||||
Linux: 'linux',
|
||||
macOs: 'mac',
|
||||
|
@ -112,58 +129,60 @@ export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async (
|
|||
};
|
||||
const enabledMalwarePolicyTypes = ['prevent', 'detect'];
|
||||
|
||||
for (const agentId of agentDailyActiveTracker.keys()) {
|
||||
const { saved_objects: agentEvents } = await getLatestFleetEndpointEvent(
|
||||
savedObjectsClient,
|
||||
agentId
|
||||
);
|
||||
// The policy details are sent as a string on the 'payload' attribute of the agent event
|
||||
const { payload } = latestEndpointEvent.attributes;
|
||||
|
||||
const latestEndpointEvent = agentEvents[0];
|
||||
if (latestEndpointEvent) {
|
||||
/*
|
||||
We can assume that if the last status of the endpoint is RUNNING and the agent has checked in within the last 24 hours
|
||||
then the endpoint has still been running within the last 24 hours.
|
||||
*/
|
||||
const { subtype, payload } = latestEndpointEvent.attributes;
|
||||
const endpointIsActive =
|
||||
subtype === 'RUNNING' && agentDailyActiveTracker.get(agentId) === true;
|
||||
|
||||
if (endpointIsActive) {
|
||||
updatedEndpointTelemetry.active_within_last_24_hours += 1;
|
||||
}
|
||||
|
||||
// The policy details are sent as a string on the 'payload' attribute of the agent event
|
||||
const endpointPolicyDetails = payload ? JSON.parse(payload) : null;
|
||||
if (endpointPolicyDetails) {
|
||||
// We get the setting the user desired to enable (treating prevent and detect as 'active' states) and then see if it succeded or failed.
|
||||
const hostType =
|
||||
policyHostTypeToPolicyType[
|
||||
endpointPolicyDetails['endpoint-security']?.host?.os?.name as EndpointOSNames
|
||||
];
|
||||
const userDesiredMalwareState =
|
||||
endpointPolicyDetails['endpoint-security'].Endpoint?.configuration?.inputs[0]?.policy[
|
||||
hostType
|
||||
]?.malware?.mode;
|
||||
|
||||
const isAnActiveMalwareState = enabledMalwarePolicyTypes.includes(userDesiredMalwareState);
|
||||
const malwareStatus =
|
||||
endpointPolicyDetails['endpoint-security'].Endpoint?.policy?.applied?.response
|
||||
?.configurations?.malware?.status;
|
||||
|
||||
if (isAnActiveMalwareState && malwareStatus !== 'failure') {
|
||||
updatedEndpointTelemetry.policies.malware.active += 1;
|
||||
}
|
||||
if (!isAnActiveMalwareState) {
|
||||
updatedEndpointTelemetry.policies.malware.inactive += 1;
|
||||
}
|
||||
if (isAnActiveMalwareState && malwareStatus === 'failure') {
|
||||
updatedEndpointTelemetry.policies.malware.failure += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!payload) {
|
||||
// This payload may not always be provided depending on the state of the endpoint. Guard again situations where it is not sent
|
||||
return policiesTracker;
|
||||
}
|
||||
|
||||
return updatedEndpointTelemetry;
|
||||
let endpointPolicyPayload;
|
||||
try {
|
||||
endpointPolicyPayload = JSON.parse(latestEndpointEvent.attributes.payload);
|
||||
} catch (error) {
|
||||
return policiesTracker;
|
||||
}
|
||||
|
||||
// Get the platform: windows, mac, or linux
|
||||
const hostType =
|
||||
policyHostTypeToPolicyType[
|
||||
endpointPolicyPayload['endpoint-security']?.host?.os?.name as EndpointOSNames
|
||||
];
|
||||
// Get whether the malware setting for the platform on the most recently provided config is active (prevent or detect is on) or off
|
||||
const userDesiredMalwareState =
|
||||
endpointPolicyPayload['endpoint-security'].Endpoint?.configuration?.inputs[0]?.policy[hostType]
|
||||
?.malware?.mode;
|
||||
|
||||
// Get the status of the application of the malware protection
|
||||
const malwareStatus =
|
||||
endpointPolicyPayload['endpoint-security'].Endpoint?.policy?.applied?.response?.configurations
|
||||
?.malware?.status;
|
||||
|
||||
if (!userDesiredMalwareState || !malwareStatus) {
|
||||
// If we get policy information without the mode or status, then nothing to track or update
|
||||
return policiesTracker;
|
||||
}
|
||||
|
||||
const updatedPoliciesTracker = {
|
||||
malware: { ...policiesTracker.malware },
|
||||
};
|
||||
|
||||
const isAnActiveMalwareState = enabledMalwarePolicyTypes.includes(userDesiredMalwareState);
|
||||
|
||||
// we only check for 'not failure' as the 'warning' state for malware is still technically actively enabled (with warnings)
|
||||
const successfullyEnabled = !!malwareStatus && malwareStatus !== 'failure';
|
||||
const failedToEnable = !!malwareStatus && malwareStatus === 'failure';
|
||||
|
||||
if (isAnActiveMalwareState && successfullyEnabled) {
|
||||
updatedPoliciesTracker.malware.active += 1;
|
||||
} else if (!isAnActiveMalwareState && successfullyEnabled) {
|
||||
updatedPoliciesTracker.malware.inactive += 1;
|
||||
} else if (isAnActiveMalwareState && failedToEnable) {
|
||||
updatedPoliciesTracker.malware.failure += 1;
|
||||
}
|
||||
|
||||
return updatedPoliciesTracker;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -173,53 +192,71 @@ export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async (
|
|||
* to confirm whether or not the endpoint is still active
|
||||
*/
|
||||
export const getEndpointTelemetryFromFleet = async (
|
||||
savedObjectsClient: ISavedObjectsRepository
|
||||
): Promise<EndpointUsage> => {
|
||||
// Retrieve every agent that references the endpoint as an installed package. It will not be listed if it was never installed
|
||||
const { saved_objects: endpointAgents } = await getFleetSavedObjectsMetadata(savedObjectsClient);
|
||||
soClient: ISavedObjectsRepository
|
||||
): Promise<EndpointUsage | {}> => {
|
||||
// Retrieve every agent (max 10000) that references the endpoint as an installed package. It will not be listed if it was never installed
|
||||
let endpointAgents;
|
||||
try {
|
||||
const response = await getFleetSavedObjectsMetadata(soClient);
|
||||
endpointAgents = response.saved_objects;
|
||||
} catch (error) {
|
||||
// Better to provide an empty object rather than default telemetry as this better informs us of an error
|
||||
return {};
|
||||
}
|
||||
|
||||
const endpointAgentsCount = endpointAgents.length;
|
||||
const endpointTelemetry = getDefaultEndpointTelemetry();
|
||||
|
||||
// If there are no installed endpoints return the default telemetry object
|
||||
if (!endpointAgents || endpointAgents.length < 1) return endpointTelemetry;
|
||||
if (!endpointAgents || endpointAgentsCount < 1) return endpointTelemetry;
|
||||
|
||||
// Use unique hosts to prevent any potential duplicates
|
||||
const uniqueHostIds: Set<string> = new Set();
|
||||
// Need agents to get events data for those that have run in last 24 hours as well as policy details
|
||||
const agentDailyActiveTracker: AgentDailyActiveTracker = new Map();
|
||||
|
||||
const aDayAgo = new Date();
|
||||
aDayAgo.setDate(aDayAgo.getDate() - 1);
|
||||
let osTracker: OSTracker = {};
|
||||
let dailyActiveCount = 0;
|
||||
let policyTracker: PoliciesTelemetry = { malware: { active: 0, inactive: 0, failure: 0 } };
|
||||
|
||||
const endpointMetadataTelemetry = endpointAgents.reduce(
|
||||
(metadataTelemetry, { attributes: metadataAttributes }) => {
|
||||
const { last_checkin: lastCheckin, local_metadata: localMetadata } = metadataAttributes;
|
||||
const { host, os, elastic } = localMetadata as AgentLocalMetadata; // AgentMetadata is just an empty blob, casting for our use case
|
||||
for (let i = 0; i < endpointAgentsCount; i += 1) {
|
||||
const { attributes: metadataAttributes } = endpointAgents[i];
|
||||
const { last_checkin: lastCheckin, local_metadata: localMetadata } = metadataAttributes;
|
||||
const { host, os, elastic } = localMetadata as AgentLocalMetadata; // AgentMetadata is just an empty blob, casting for our use case
|
||||
|
||||
if (host && uniqueHostIds.has(host.id)) {
|
||||
// use hosts since new agents could potentially be re-installed on existing hosts
|
||||
return metadataTelemetry;
|
||||
} else {
|
||||
uniqueHostIds.add(host.id);
|
||||
const isActiveWithinLastDay = !!lastCheckin && new Date(lastCheckin) > aDayAgo;
|
||||
agentDailyActiveTracker.set(elastic.agent.id, isActiveWithinLastDay);
|
||||
osTracker = trackEndpointOSTelemetry(os, osTracker);
|
||||
return metadataTelemetry;
|
||||
if (!uniqueHostIds.has(host.id)) {
|
||||
uniqueHostIds.add(host.id);
|
||||
const agentId = elastic?.agent?.id;
|
||||
osTracker = updateEndpointOSTelemetry(os, osTracker);
|
||||
|
||||
if (agentId) {
|
||||
let agentEvents;
|
||||
try {
|
||||
const response = await getLatestFleetEndpointEvent(soClient, agentId);
|
||||
agentEvents = response.saved_objects;
|
||||
} catch (error) {
|
||||
// If the request fails we do not obtain `active within last 24 hours for this agent` or policy specifics
|
||||
}
|
||||
|
||||
// AgentEvents will have a max length of 1
|
||||
if (agentEvents && agentEvents.length > 0) {
|
||||
const latestEndpointEvent = agentEvents[0];
|
||||
dailyActiveCount = updateEndpointDailyActiveCount(
|
||||
latestEndpointEvent,
|
||||
lastCheckin,
|
||||
dailyActiveCount
|
||||
);
|
||||
policyTracker = updateEndpointPolicyTelemetry(latestEndpointEvent, policyTracker);
|
||||
}
|
||||
}
|
||||
},
|
||||
endpointTelemetry
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All unique hosts with an endpoint installed.
|
||||
// All unique hosts with an endpoint installed, thus all unique endpoint installs
|
||||
endpointTelemetry.total_installed = uniqueHostIds.size;
|
||||
// Set the daily active count for the endpoints
|
||||
endpointTelemetry.active_within_last_24_hours = dailyActiveCount;
|
||||
// Get the objects to populate our OS Telemetry
|
||||
endpointMetadataTelemetry.os = Object.values(osTracker);
|
||||
// Populate endpoint telemetry with the finalized 24 hour count and policy details
|
||||
const finalizedEndpointTelemetryData = await addEndpointDailyActivityAndPolicyDetailsToTelemetry(
|
||||
agentDailyActiveTracker,
|
||||
savedObjectsClient,
|
||||
endpointMetadataTelemetry
|
||||
);
|
||||
endpointTelemetry.os = Object.values(osTracker);
|
||||
// Provide the updated policy information
|
||||
endpointTelemetry.policies = policyTracker;
|
||||
|
||||
return finalizedEndpointTelemetryData;
|
||||
return endpointTelemetry;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue