diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index b17a5aa28ac6a3..2d004d3315beb7 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -61,14 +61,20 @@ const Mac: HostOS[] = []; const OS: HostOS[] = [...Windows, ...Mac, ...Linux]; -const POLICIES: Array<{ name: string; id: string }> = [ +const APPLIED_POLICIES: Array<{ + name: string; + id: string; + status: HostPolicyResponseActionStatus; +}> = [ { name: 'Default', id: '00000000-0000-0000-0000-000000000000', + status: HostPolicyResponseActionStatus.success, }, { name: 'With Eventing', id: 'C2A9093E-E289-4C0A-AA44-8C32A414FA7A', + status: HostPolicyResponseActionStatus.success, }, ]; @@ -181,7 +187,11 @@ interface HostInfo { host: Host; endpoint: { policy: { - id: string; + applied: { + id: string; + status: HostPolicyResponseActionStatus; + name: string; + }; }; }; } @@ -271,7 +281,12 @@ export class EndpointDocGenerator { * Creates new random policy id for the host to simulate new policy application */ public updatePolicyId() { - this.commonInfo.endpoint.policy.id = this.randomChoice(POLICIES).id; + this.commonInfo.endpoint.policy.applied.id = this.randomChoice(APPLIED_POLICIES).id; + this.commonInfo.endpoint.policy.applied.status = this.randomChoice([ + HostPolicyResponseActionStatus.success, + HostPolicyResponseActionStatus.failure, + HostPolicyResponseActionStatus.warning, + ]); } private createHostData(): HostInfo { @@ -293,7 +308,9 @@ export class EndpointDocGenerator { os: this.randomChoice(OS), }, endpoint: { - policy: this.randomChoice(POLICIES), + policy: { + applied: this.randomChoice(APPLIED_POLICIES), + }, }, }; } @@ -974,7 +991,7 @@ export class EndpointDocGenerator { status: HostPolicyResponseActionStatus.success, }, ], - id: this.commonInfo.endpoint.policy.id, + id: this.commonInfo.endpoint.policy.applied.id, response: { configurations: { events: { @@ -1015,8 +1032,9 @@ export class EndpointDocGenerator { ], }, }, - status: this.randomHostPolicyResponseActionStatus(), + status: this.commonInfo.endpoint.policy.applied.status, version: policyVersion, + name: this.commonInfo.endpoint.policy.applied.name, }, }, }, diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts index 816f9b77115ec3..cfbf8f176b32d3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types.ts @@ -253,7 +253,11 @@ export type AlertEvent = Immutable<{ }; endpoint: { policy: { - id: string; + applied: { + id: string; + status: HostPolicyResponseActionStatus; + name: string; + }; }; }; process: { @@ -357,7 +361,11 @@ export type HostMetadata = Immutable<{ }; endpoint: { policy: { - id: string; + applied: { + id: string; + status: HostPolicyResponseActionStatus; + name: string; + }; }; }; agent: { @@ -700,6 +708,7 @@ export interface HostPolicyResponse { applied: { version: string; id: string; + name: string; status: HostPolicyResponseActionStatus; actions: HostPolicyResponseAppliedAction[]; response: { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index 9b0ca73cf021f5..a3862d4454c1d1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -86,7 +86,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policy', { defaultMessage: 'Policy', }), - description: details.endpoint.policy.id, + description: details.endpoint.policy.applied.id, }, { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policyStatus', { @@ -138,10 +138,10 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { }, ]; }, [ - details.endpoint.policy.id, - details.host.ip, - details.host.hostname, details.agent.version, + details.endpoint.policy.applied.id, + details.host.hostname, + details.host.ip, policyStatus, policyResponseUri, policyStatusClickHandler, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index b2f5866a3ae7d5..9b9d4a74e5970c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -27,7 +27,6 @@ import { } from '../../../../common/endpoint/types'; import { SearchResponse } from 'elasticsearch'; import { registerEndpointRoutes } from './index'; -import * as data from '../../test_data/all_metadata_data.json'; import { createMockAgentService, createMockMetadataIndexPatternRetriever, @@ -37,6 +36,7 @@ import { AgentService } from '../../../../../ingest_manager/server'; import Boom from 'boom'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; +import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; describe('test endpoint route', () => { let routerMock: jest.Mocked; @@ -78,10 +78,7 @@ describe('test endpoint route', () => { it('test find the latest of all endpoints', async () => { const mockRequest = httpServerMock.createKibanaRequest({}); - - const response: SearchResponse = (data as unknown) as SearchResponse< - HostMetadata - >; + const response = createSearchResponse(new EndpointDocGenerator().generateHostMetadata()); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/metadata') @@ -97,8 +94,8 @@ describe('test endpoint route', () => { expect(routeConfig.options).toEqual({ authRequired: true }); expect(mockResponse.ok).toBeCalled(); const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(2); - expect(endpointResultList.total).toEqual(2); + expect(endpointResultList.hosts.length).toEqual(1); + expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(0); expect(endpointResultList.request_page_size).toEqual(10); }); @@ -119,7 +116,7 @@ describe('test endpoint route', () => { mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => - Promise.resolve((data as unknown) as SearchResponse) + Promise.resolve(createSearchResponse(new EndpointDocGenerator().generateHostMetadata())) ); [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/metadata') @@ -138,8 +135,8 @@ describe('test endpoint route', () => { expect(routeConfig.options).toEqual({ authRequired: true }); expect(mockResponse.ok).toBeCalled(); const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(2); - expect(endpointResultList.total).toEqual(2); + expect(endpointResultList.hosts.length).toEqual(1); + expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); }); @@ -162,7 +159,7 @@ describe('test endpoint route', () => { mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => - Promise.resolve((data as unknown) as SearchResponse) + Promise.resolve(createSearchResponse(new EndpointDocGenerator().generateHostMetadata())) ); [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/metadata') @@ -194,8 +191,8 @@ describe('test endpoint route', () => { expect(routeConfig.options).toEqual({ authRequired: true }); expect(mockResponse.ok).toBeCalled(); const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(2); - expect(endpointResultList.total).toEqual(2); + expect(endpointResultList.hosts.length).toEqual(1); + expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); }); @@ -203,25 +200,9 @@ describe('test endpoint route', () => { describe('Endpoint Details route', () => { it('should return 404 on no results', async () => { const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } }); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => - Promise.resolve({ - took: 3, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 9, - relation: 'eq', - }, - max_score: null, - hits: [], - }, - }) + Promise.resolve(createSearchResponse()) ); mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => @@ -241,13 +222,10 @@ describe('test endpoint route', () => { }); it('should return a single endpoint with status online', async () => { + const response = createSearchResponse(new EndpointDocGenerator().generateHostMetadata()); const mockRequest = httpServerMock.createKibanaRequest({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: { id: (data as any).hits.hits[0]._id }, + params: { id: response.hits.hits[0]._id }, }); - const response: SearchResponse = (data as unknown) as SearchResponse< - HostMetadata - >; mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('online'); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => @@ -269,9 +247,7 @@ describe('test endpoint route', () => { }); it('should return a single endpoint with status error when AgentService throw 404', async () => { - const response: SearchResponse = (data as unknown) as SearchResponse< - HostMetadata - >; + const response = createSearchResponse(new EndpointDocGenerator().generateHostMetadata()); const mockRequest = httpServerMock.createKibanaRequest({ params: { id: response.hits.hits[0]._id }, @@ -299,9 +275,7 @@ describe('test endpoint route', () => { }); it('should return a single endpoint with status error when status is not offline or online', async () => { - const response: SearchResponse = (data as unknown) as SearchResponse< - HostMetadata - >; + const response = createSearchResponse(new EndpointDocGenerator().generateHostMetadata()); const mockRequest = httpServerMock.createKibanaRequest({ params: { id: response.hits.hits[0]._id }, @@ -327,3 +301,59 @@ describe('test endpoint route', () => { }); }); }); + +function createSearchResponse(hostMetadata?: HostMetadata): SearchResponse { + return ({ + took: 15, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 5, + relation: 'eq', + }, + max_score: null, + hits: hostMetadata + ? [ + { + _index: 'metrics-endpoint.metadata-default-1', + _id: '8FhM0HEBYyRTvb6lOQnw', + _score: null, + _source: hostMetadata, + sort: [1588337587997], + inner_hits: { + most_recent: { + hits: { + total: { + value: 2, + relation: 'eq', + }, + max_score: null, + hits: [ + { + _index: 'metrics-endpoint.metadata-default-1', + _id: 'W6Vo1G8BYQH1gtPUgYkC', + _score: null, + _source: hostMetadata, + sort: [1579816615336], + }, + ], + }, + }, + }, + }, + ] + : [], + }, + aggregations: { + total: { + value: 1, + }, + }, + } as unknown) as SearchResponse; +} diff --git a/x-pack/plugins/security_solution/server/endpoint/test_data/all_metadata_data.json b/x-pack/plugins/security_solution/server/endpoint/test_data/all_metadata_data.json deleted file mode 100644 index 48952afb33f687..00000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/test_data/all_metadata_data.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "took": 343, - "timed_out": false, - "_shards": { - "total": 1, - "successful": 1, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": { - "value": 4, - "relation": "eq" - }, - "max_score": null, - "hits": [ - { - "_index": "metadata-endpoint-default-1", - "_id": "WqVo1G8BYQH1gtPUgYkC", - "_score": null, - "_source": { - "@timestamp": 1579816615336, - "event": { - "created": "2020-01-23T21:56:55.336Z" - }, - "elastic": { - "agent": { - "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2" - } - }, - "endpoint": { - "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" - } - }, - "agent": { - "version": "6.8.3", - "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2", - "name": "Elastic Endpoint" - }, - "host": { - "id": "7141a48b-e19f-4ae3-89a0-6e7179a84265", - "hostname": "larimer-0.example.com", - "ip": "10.21.48.136", - "mac": "77-be-30-f0-e8-d6", - "architecture": "x86_64", - "os": { - "name": "windows 6.2", - "full": "Windows Server 2012", - "version": "6.2", - "variant": "Windows Server" - } - } - }, - "fields": { - "host.id.keyword": ["7141a48b-e19f-4ae3-89a0-6e7179a84265"] - }, - "sort": [1579816615336], - "inner_hits": { - "most_recent": { - "hits": { - "total": { - "value": 2, - "relation": "eq" - }, - "max_score": null, - "hits": [ - { - "_index": "metadata-endpoint-default-1", - "_id": "WqVo1G8BYQH1gtPUgYkC", - "_score": null, - "_source": { - "@timestamp": 1579816615336, - "event": { - "created": "2020-01-23T21:56:55.336Z" - }, - "elastic": { - "agent": { - "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2" - } - }, - "endpoint": { - "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" - } - }, - "agent": { - "version": "6.8.3", - "id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2", - "name": "Elastic Endpoint" - }, - "host": { - "id": "7141a48b-e19f-4ae3-89a0-6e7179a84265", - "hostname": "larimer-0.example.com", - "ip": "10.21.48.136", - "mac": "77-be-30-f0-e8-d6", - "architecture": "x86_64", - "os": { - "name": "windows 6.2", - "full": "Windows Server 2012", - "version": "6.2", - "variant": "Windows Server" - } - } - }, - "sort": [1579816615336] - } - ] - } - } - } - }, - { - "_index": "metadata-endpoint-default-1", - "_id": "W6Vo1G8BYQH1gtPUgYkC", - "_score": null, - "_source": { - "@timestamp": 1579816615336, - "event": { - "created": "2020-01-23T21:56:55.336Z" - }, - "elastic": { - "agent": { - "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312" - } - }, - "endpoint": { - "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" - } - }, - "agent": { - "version": "6.4.3", - "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312", - "name": "Elastic Endpoint" - }, - "host": { - "id": "f35ec6c1-6562-45b1-818f-2f14c0854adf", - "hostname": "hildebrandt-6.example.com", - "ip": "10.53.92.84", - "mac": "af-f1-8f-51-25-2a", - "architecture": "x86_64", - "os": { - "name": "windows 10.0", - "full": "Windows 10", - "version": "10.0", - "variant": "Windows Pro" - } - } - }, - "fields": { - "host.id.keyword": ["f35ec6c1-6562-45b1-818f-2f14c0854adf"] - }, - "sort": [1579816615336], - "inner_hits": { - "most_recent": { - "hits": { - "total": { - "value": 2, - "relation": "eq" - }, - "max_score": null, - "hits": [ - { - "_index": "metadata-endpoint-default-1", - "_id": "W6Vo1G8BYQH1gtPUgYkC", - "_score": null, - "_source": { - "@timestamp": 1579816615336, - "event": { - "created": "2020-01-23T21:56:55.336Z" - }, - "elastic": { - "agent": { - "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312" - } - }, - "endpoint": { - "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" - } - }, - "agent": { - "version": "6.4.3", - "id": "c2d84d8f-d355-40de-8b54-5d318d4d1312", - "name": "Elastic Endpoint" - }, - "host": { - "id": "f35ec6c1-6562-45b1-818f-2f14c0854adf", - "hostname": "hildebrandt-6.example.com", - "ip": "10.53.92.84", - "mac": "af-f1-8f-51-25-2a", - "architecture": "x86_64", - "os": { - "name": "windows 10.0", - "full": "Windows 10", - "version": "10.0", - "variant": "Windows Pro" - } - } - }, - "sort": [1579816615336] - } - ] - } - } - } - } - ] - }, - "aggregations": { - "total": { - "value": 2 - } - } -} diff --git a/x-pack/test/api_integration/apis/endpoint/metadata.ts b/x-pack/test/api_integration/apis/endpoint/metadata.ts index 6c6db10729ab6c..61f294cbd6f9c8 100644 --- a/x-pack/test/api_integration/apis/endpoint/metadata.ts +++ b/x-pack/test/api_integration/apis/endpoint/metadata.ts @@ -199,6 +199,24 @@ export default function ({ getService }: FtrProviderContext) { expect(body.request_page_index).to.eql(0); }); + it('metadata api should return the latest event for all the events where policy status is not success', async () => { + const { body } = await supertest + .post('/api/endpoint/metadata') + .set('kbn-xsrf', 'xxx') + .send({ + filter: `not endpoint.policy.applied.status:success`, + }) + .expect(200); + const statuses: Set = new Set( + body.hosts.map( + (hostInfo: Record) => hostInfo.metadata.endpoint.policy.applied.status + ) + ); + + expect(statuses.size).to.eql(1); + expect(Array.from(statuses)).to.eql(['failure']); + }); + it('metadata api should return the endpoint based on the elastic agent id, and status should be error', async () => { const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf'; const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095'; diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz index 49082ed3bec8b0..2921abededde5a 100644 Binary files a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz and b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json index 0f9f86b36dec79..a8d868ebbec15f 100644 --- a/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json +++ b/x-pack/test/functional/es_archives/endpoint/metadata/api_feature/data.json @@ -17,7 +17,11 @@ }, "endpoint": { "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + "applied": { + "name": "Default", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", + "status": "failure" + } } }, "event": { @@ -66,7 +70,11 @@ }, "endpoint": { "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + "applied": { + "name": "Default", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", + "status": "success" + } } }, "event": { @@ -114,7 +122,11 @@ }, "endpoint": { "policy": { - "id": "00000000-0000-0000-0000-000000000000" + "applied": { + "name": "Default", + "id": "00000000-0000-0000-0000-000000000000", + "status": "failure" + } } }, "event": { @@ -160,7 +172,11 @@ }, "endpoint": { "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + "applied": { + "name": "Default", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", + "status": "failure" + } } }, "event": { @@ -209,7 +225,11 @@ }, "endpoint": { "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + "applied": { + "name": "Default", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", + "status": "success" + } } }, "event": { @@ -256,7 +276,11 @@ }, "endpoint": { "policy": { - "id": "00000000-0000-0000-0000-000000000000" + "applied": { + "name": "With Eventing", + "id": "00000000-0000-0000-0000-000000000000", + "status": "failure" + } } }, "event": { @@ -303,7 +327,11 @@ }, "endpoint": { "policy": { - "id": "00000000-0000-0000-0000-000000000000" + "applied": { + "name": "With Eventing", + "id": "00000000-0000-0000-0000-000000000000", + "status": "failure" + } } }, "event": { @@ -351,7 +379,11 @@ }, "endpoint": { "policy": { - "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + "applied": { + "name": "Default", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", + "status": "success" + } } }, "event": { @@ -398,7 +430,11 @@ }, "endpoint": { "policy": { - "id": "00000000-0000-0000-0000-000000000000" + "applied": { + "name": "With Eventing", + "id": "00000000-0000-0000-0000-000000000000", + "status": "success" + } } }, "event": { diff --git a/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz index 2fab424d27cad6..f380785f021bbf 100644 Binary files a/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz and b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz differ