diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index bb3583d50f8e5..9740f57450e80 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -66,8 +66,8 @@ export const registerCollector: RegisterCollector = ({ }, policies: { malware: { - success: { type: 'long' }, - warning: { type: 'long' }, + active: { type: 'long' }, + inactive: { type: 'long' }, failure: { type: 'long' }, }, }, diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts index 9a5ebea01ea43..1369a3d398265 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.mocks.ts @@ -80,6 +80,55 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => JSON.stringify({ 'endpoint-security': { Endpoint: { + configuration: { + inputs: [ + { + id: '0d466df0-c60f-11ea-a5c5-151665e785c4', + policy: { + linux: { + events: { + file: true, + network: true, + process: true, + }, + logging: { + file: 'info', + }, + }, + mac: { + events: { + file: true, + network: true, + process: true, + }, + logging: { + file: 'info', + }, + malware: { + mode: 'prevent', + }, + }, + windows: { + events: { + dll_and_driver_load: true, + dns: true, + file: true, + network: true, + process: true, + registry: true, + security: true, + }, + logging: { + file: 'info', + }, + malware: { + mode: 'prevent', + }, + }, + }, + }, + ], + }, policy: { applied: { id: '0d466df0-c60f-11ea-a5c5-151665e785c4', @@ -114,6 +163,18 @@ const mockPolicyPayload = (malwareStatus: 'success' | 'warning' | 'failure') => id: 'testAgentId', version: '8.0.0-SNAPSHOT', }, + host: { + architecture: 'x86_64', + id: 'a4148b63-1758-ab1f-a6d3-f95075cb1a9c', + os: { + Ext: { + variant: 'Windows 10 Pro', + }, + full: 'Windows 10 Pro 2004 (10.0.19041.329)', + name: 'Windows', + version: '2004 (10.0.19041.329)', + }, + }, }, }); diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts index c6cdc6d85b7c5..06755192bd818 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/endpoint.test.ts @@ -41,9 +41,9 @@ describe('test security solution endpoint telemetry', () => { "os": Array [], "policies": Object { "malware": Object { + "active": 0, "failure": 0, - "success": 0, - "warning": 0, + "inactive": 0, }, }, "total_installed": 0, @@ -68,8 +68,8 @@ describe('test security solution endpoint telemetry', () => { policies: { malware: { failure: 0, - success: 0, - warning: 0, + active: 0, + inactive: 0, }, }, }); @@ -102,8 +102,8 @@ describe('test security solution endpoint telemetry', () => { policies: { malware: { failure: 1, - success: 0, - warning: 0, + active: 0, + inactive: 0, }, }, }); @@ -134,8 +134,8 @@ describe('test security solution endpoint telemetry', () => { policies: { malware: { failure: 0, - success: 1, - warning: 0, + active: 1, + inactive: 0, }, }, }); diff --git a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts index f26f22885919e..ab5669d503275 100644 --- a/x-pack/plugins/security_solution/server/usage/endpoints/index.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoints/index.ts @@ -14,15 +14,15 @@ export interface AgentOSMetadataTelemetry { version: string; count: number; } - -export type PolicyTypes = 'malware'; export interface PolicyTelemetry { - success: number; - warning: number; + active: number; + inactive: number; failure: number; } -export type PoliciesTelemetry = Record; +export interface PoliciesTelemetry { + malware: PolicyTelemetry; +} export interface EndpointUsage { total_installed: number; @@ -31,6 +31,8 @@ export interface EndpointUsage { policies: PoliciesTelemetry; } +type EndpointOSNames = 'Linux' | 'Windows' | 'macOs'; + export interface AgentLocalMetadata extends AgentMetadata { elastic: { agent: { @@ -48,8 +50,8 @@ export interface AgentLocalMetadata extends AgentMetadata { }; } -export type OSTracker = Record; - +type OSTracker = Record; +type AgentDailyActiveTracker = Map; /** * @description returns an empty telemetry object to be incrmented and updated within the `getEndpointTelemetryFromFleet` fn */ @@ -59,13 +61,16 @@ export const getDefaultEndpointTelemetry = (): EndpointUsage => ({ os: [], policies: { malware: { - success: 0, - warning: 0, + active: 0, + inactive: 0, failure: 0, }, }, }); +/** + * @description this fun + */ export const trackEndpointOSTelemetry = ( os: AgentLocalMetadata['os'], osTracker: OSTracker @@ -87,6 +92,80 @@ export const trackEndpointOSTelemetry = ( return updatedOSTracker; }; +/** + * @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. + */ +export const addEndpointDailyActivityAndPolicyDetailsToTelemetry = async ( + agentDailyActiveTracker: AgentDailyActiveTracker, + savedObjectsClient: ISavedObjectsRepository, + endpointTelemetry: EndpointUsage +): Promise => { + const updatedEndpointTelemetry = { ...endpointTelemetry }; + + const policyHostTypeToPolicyType = { + Linux: 'linux', + macOs: 'mac', + Windows: 'windows', + }; + const enabledMalwarePolicyTypes = ['prevent', 'detect']; + + for (const agentId of agentDailyActiveTracker.keys()) { + const { saved_objects: agentEvents } = await getLatestFleetEndpointEvent( + savedObjectsClient, + agentId + ); + + 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; + } + } + } + } + + return updatedEndpointTelemetry; +}; + /** * @description This aggregates the telemetry details from the two fleet savedObject sources, `fleet-agents` and `fleet-agent-events` to populate * the telemetry details for endpoint. Since we cannot access our own indices due to `kibana_system` not having access, this is the best alternative. @@ -106,7 +185,7 @@ export const getEndpointTelemetryFromFleet = async ( // Use unique hosts to prevent any potential duplicates const uniqueHostIds: Set = new Set(); // Need agents to get events data for those that have run in last 24 hours as well as policy details - const agentDailyActiveTracker: Map = new Map(); + const agentDailyActiveTracker: AgentDailyActiveTracker = new Map(); const aDayAgo = new Date(); aDayAgo.setDate(aDayAgo.getDate() - 1); @@ -131,48 +210,16 @@ export const getEndpointTelemetryFromFleet = async ( endpointTelemetry ); - // All unique agents with an endpoint installed. You can technically install a new agent on a host, so relying on most recently installed. + // All unique hosts with an endpoint installed. endpointTelemetry.total_installed = uniqueHostIds.size; // 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 + ); - for (const agentId of agentDailyActiveTracker.keys()) { - const { saved_objects: agentEvents } = await getLatestFleetEndpointEvent( - savedObjectsClient, - agentId - ); - - 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) { - endpointMetadataTelemetry.active_within_last_24_hours += 1; - } - const endpointPolicyDetails = payload ? JSON.parse(payload) : null; - if (endpointPolicyDetails) { - const malwareStatus = - endpointPolicyDetails['endpoint-security'].Endpoint?.policy?.applied?.response - ?.configurations?.malware?.status; - - if (malwareStatus === 'success') { - endpointMetadataTelemetry.policies.malware.success += 1; - } - if (malwareStatus === 'warning') { - endpointMetadataTelemetry.policies.malware.warning += 1; - } - if (malwareStatus === 'failure') { - endpointMetadataTelemetry.policies.malware.failure += 1; - } - } - } - } - - return endpointMetadataTelemetry; + return finalizedEndpointTelemetryData; }; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index a7bc29f9efae2..fd21b70660bb6 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -246,10 +246,10 @@ "properties": { "malware": { "properties": { - "success": { + "active": { "type": "long" }, - "warning": { + "inactive": { "type": "long" }, "failure": {