mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SecuritySolution] Get endpoint metadata (#99452)
* getHostEndpoint * add endpointContext * add deps * get endpoint info * clean up * fix tests error * fix types * fix unit tests * fix unit tests * fix unit tests * fix types error * fix types * fix api integration test * fix api integration tests * add comment * review * add getHostInfo * rename getHostInfo into getHostMetaData Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
65a2177dcf
commit
5893d67b4b
29 changed files with 516 additions and 185 deletions
|
@ -414,6 +414,11 @@ export type PolicyInfo = Immutable<{
|
|||
id: string;
|
||||
}>;
|
||||
|
||||
export interface HostMetaDataInfo {
|
||||
metadata: HostMetadata;
|
||||
query_strategy_version: MetadataQueryStrategyVersions;
|
||||
}
|
||||
|
||||
export type HostInfo = Immutable<{
|
||||
metadata: HostMetadata;
|
||||
host_status: HostStatus;
|
||||
|
|
|
@ -25,10 +25,16 @@ export interface EndpointFields {
|
|||
endpointPolicy?: Maybe<string>;
|
||||
sensorVersion?: Maybe<string>;
|
||||
policyStatus?: Maybe<HostPolicyResponseActionStatus>;
|
||||
id?: Maybe<string>;
|
||||
}
|
||||
|
||||
interface AgentFields {
|
||||
id?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface HostItem {
|
||||
_id?: Maybe<string>;
|
||||
agent?: Maybe<AgentFields>;
|
||||
cloud?: Maybe<CloudEcs>;
|
||||
endpoint?: Maybe<EndpointFields>;
|
||||
host?: Maybe<HostEcs>;
|
||||
|
@ -70,6 +76,9 @@ export interface HostAggEsItem {
|
|||
cloud_machine_type?: HostBuckets;
|
||||
cloud_provider?: HostBuckets;
|
||||
cloud_region?: HostBuckets;
|
||||
endpoint?: {
|
||||
id: HostBuckets;
|
||||
};
|
||||
host_architecture?: HostBuckets;
|
||||
host_id?: HostBuckets;
|
||||
host_ip?: HostBuckets;
|
||||
|
|
|
@ -9,6 +9,9 @@ import { HostItem } from '../../../../../common/search_strategy/security_solutio
|
|||
import { CriteriaFields } from '../types';
|
||||
|
||||
export const hostToCriteria = (hostItem: HostItem): CriteriaFields[] => {
|
||||
if (hostItem == null) {
|
||||
return [];
|
||||
}
|
||||
if (hostItem.host != null && hostItem.host.name != null) {
|
||||
const criteria: CriteriaFields[] = [
|
||||
{
|
||||
|
|
|
@ -145,14 +145,14 @@ export const useHostDetails = ({
|
|||
}
|
||||
return prevRequest;
|
||||
});
|
||||
return () => {
|
||||
searchSubscription$.current.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
}, [endDate, hostName, indexNames, startDate]);
|
||||
|
||||
useEffect(() => {
|
||||
hostDetailsSearch(hostDetailsRequest);
|
||||
return () => {
|
||||
searchSubscription$.current.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
}, [hostDetailsRequest, hostDetailsSearch]);
|
||||
|
||||
return [loading, hostDetailsResponse];
|
||||
|
|
|
@ -50,6 +50,9 @@ import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
|
|||
import { useSourcererScope } from '../../../common/containers/sourcerer';
|
||||
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useHostDetails } from '../../containers/hosts/details';
|
||||
import { manageQuery } from '../../../common/components/page/manage_query';
|
||||
|
||||
const HostOverviewManage = manageQuery(HostOverview);
|
||||
|
||||
const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDetailsPagePath }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -93,11 +96,12 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
);
|
||||
|
||||
const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope();
|
||||
const [loading, { hostDetails: hostOverview, id }] = useHostDetails({
|
||||
const [loading, { hostDetails: hostOverview, id, refetch }] = useHostDetails({
|
||||
endDate: to,
|
||||
startDate: from,
|
||||
hostName: detailName,
|
||||
indexNames: selectedPatterns,
|
||||
skip: selectedPatterns.length === 0,
|
||||
});
|
||||
const filterQuery = convertToBuildEsQuery({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
|
@ -141,7 +145,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
<HostOverview
|
||||
<HostOverviewManage
|
||||
docValueFields={docValueFields}
|
||||
id={id}
|
||||
isInDetailsSidePanel={false}
|
||||
|
@ -160,6 +164,8 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
to: fromTo.to,
|
||||
});
|
||||
}}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
|
|
|
@ -321,7 +321,7 @@ export const EndpointList = () => {
|
|||
render: (hostStatus: HostInfo['host_status']) => {
|
||||
return (
|
||||
<EuiBadge
|
||||
color={HOST_STATUS_TO_BADGE_COLOR[hostStatus] || 'warning'}
|
||||
color={hostStatus != null ? HOST_STATUS_TO_BADGE_COLOR[hostStatus] : 'warning'}
|
||||
data-test-subj="rowHostStatus"
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
|
|
|
@ -86,14 +86,15 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
() => [
|
||||
{
|
||||
title: i18n.HOST_ID,
|
||||
description: data.host
|
||||
? hostIdRenderer({ host: data.host, noLink: true })
|
||||
: getEmptyTagValue(),
|
||||
description:
|
||||
data && data.host
|
||||
? hostIdRenderer({ host: data.host, noLink: true })
|
||||
: getEmptyTagValue(),
|
||||
},
|
||||
{
|
||||
title: i18n.FIRST_SEEN,
|
||||
description:
|
||||
data.host != null && data.host.name && data.host.name.length ? (
|
||||
data && data.host != null && data.host.name && data.host.name.length ? (
|
||||
<FirstLastSeenHost
|
||||
docValueFields={docValueFields}
|
||||
hostName={data.host.name[0]}
|
||||
|
@ -107,7 +108,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
{
|
||||
title: i18n.LAST_SEEN,
|
||||
description:
|
||||
data.host != null && data.host.name && data.host.name.length ? (
|
||||
data && data.host != null && data.host.name && data.host.name.length ? (
|
||||
<FirstLastSeenHost
|
||||
docValueFields={docValueFields}
|
||||
hostName={data.host.name[0]}
|
||||
|
@ -221,7 +222,7 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
)}
|
||||
</OverviewWrapper>
|
||||
</InspectButtonContainer>
|
||||
{data.endpoint != null ? (
|
||||
{data && data.endpoint != null ? (
|
||||
<>
|
||||
<EuiHorizontalRule />
|
||||
<OverviewWrapper direction={isInDetailsSidePanel ? 'column' : 'row'}>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
|
||||
import { loggingSystemMock, savedObjectsServiceMock } from '../../../../../src/core/server/mocks';
|
||||
import { IScopedClusterClient, SavedObjectsClientContract } from '../../../../../src/core/server';
|
||||
import { listMock } from '../../../lists/server/mocks';
|
||||
import { securityMock } from '../../../security/server/mocks';
|
||||
import { alertsMock } from '../../../alerting/server/mocks';
|
||||
|
@ -131,11 +131,11 @@ export const createMockMetadataRequestContext = (): jest.Mocked<MetadataRequestC
|
|||
};
|
||||
|
||||
export function createRouteHandlerContext(
|
||||
dataClient: jest.Mocked<ILegacyScopedClusterClient>,
|
||||
dataClient: jest.Mocked<IScopedClusterClient>,
|
||||
savedObjectsClient: jest.Mocked<SavedObjectsClientContract>
|
||||
) {
|
||||
const context = xpackMocks.createRequestHandlerContext();
|
||||
context.core.elasticsearch.legacy.client = dataClient;
|
||||
const context = (xpackMocks.createRequestHandlerContext() as unknown) as jest.Mocked<SecuritySolutionRequestHandlerContext>;
|
||||
context.core.elasticsearch.client = dataClient;
|
||||
context.core.savedObjects.client = savedObjectsClient;
|
||||
return context;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,18 @@
|
|||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import type { Logger, RequestHandler } from 'kibana/server';
|
||||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import {
|
||||
IScopedClusterClient,
|
||||
Logger,
|
||||
RequestHandler,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../../../../src/core/server';
|
||||
import {
|
||||
HostInfo,
|
||||
HostMetadata,
|
||||
HostMetaDataInfo,
|
||||
HostResultList,
|
||||
HostStatus,
|
||||
MetadataQueryStrategyVersions,
|
||||
|
@ -27,9 +34,11 @@ import { findAgentIDsByStatus } from './support/agent_status';
|
|||
import { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
|
||||
export interface MetadataRequestContext {
|
||||
esClient?: IScopedClusterClient;
|
||||
endpointAppContextService: EndpointAppContextService;
|
||||
logger: Logger;
|
||||
requestHandlerContext: SecuritySolutionRequestHandlerContext;
|
||||
requestHandlerContext?: SecuritySolutionRequestHandlerContext;
|
||||
savedObjectsClient?: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
const HOST_STATUS_MAPPING = new Map<AgentStatus, HostStatus>([
|
||||
|
@ -75,9 +84,11 @@ export const getMetadataListRequestHandler = function (
|
|||
}
|
||||
|
||||
const metadataRequestContext: MetadataRequestContext = {
|
||||
esClient: context.core.elasticsearch.client,
|
||||
endpointAppContextService: endpointAppContext.service,
|
||||
logger,
|
||||
requestHandlerContext: context,
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
};
|
||||
|
||||
const unenrolledAgentIds = await findAllUnenrolledAgentIds(
|
||||
|
@ -110,9 +121,10 @@ export const getMetadataListRequestHandler = function (
|
|||
}
|
||||
);
|
||||
|
||||
const hostListQueryResult = queryStrategy!.queryResponseToHostListResult(
|
||||
await context.core.elasticsearch.legacy.client.callAsCurrentUser('search', queryParams)
|
||||
const result = await context.core.elasticsearch.client.asCurrentUser.search<HostMetadata>(
|
||||
queryParams
|
||||
);
|
||||
const hostListQueryResult = queryStrategy!.queryResponseToHostListResult(result.body);
|
||||
return response.ok({
|
||||
body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext),
|
||||
});
|
||||
|
@ -136,9 +148,11 @@ export const getMetadataRequestHandler = function (
|
|||
}
|
||||
|
||||
const metadataRequestContext: MetadataRequestContext = {
|
||||
esClient: context.core.elasticsearch.client,
|
||||
endpointAppContextService: endpointAppContext.service,
|
||||
logger,
|
||||
requestHandlerContext: context,
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -164,42 +178,86 @@ export const getMetadataRequestHandler = function (
|
|||
};
|
||||
};
|
||||
|
||||
export async function getHostData(
|
||||
export async function getHostMetaData(
|
||||
metadataRequestContext: MetadataRequestContext,
|
||||
id: string,
|
||||
queryStrategyVersion?: MetadataQueryStrategyVersions
|
||||
): Promise<HostInfo | undefined> {
|
||||
): Promise<HostMetaDataInfo | undefined> {
|
||||
if (
|
||||
!metadataRequestContext.esClient &&
|
||||
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
|
||||
) {
|
||||
throw Boom.badRequest('esClient not found');
|
||||
}
|
||||
|
||||
if (
|
||||
!metadataRequestContext.savedObjectsClient &&
|
||||
!metadataRequestContext.requestHandlerContext?.core.savedObjects
|
||||
) {
|
||||
throw Boom.badRequest('savedObjectsClient not found');
|
||||
}
|
||||
|
||||
const esClient = (metadataRequestContext?.esClient ??
|
||||
metadataRequestContext.requestHandlerContext?.core.elasticsearch
|
||||
.client) as IScopedClusterClient;
|
||||
|
||||
const esSavedObjectClient =
|
||||
metadataRequestContext?.savedObjectsClient ??
|
||||
(metadataRequestContext.requestHandlerContext?.core.savedObjects
|
||||
.client as SavedObjectsClientContract);
|
||||
|
||||
const queryStrategy = await metadataRequestContext.endpointAppContextService
|
||||
?.getMetadataService()
|
||||
?.queryStrategy(
|
||||
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
|
||||
queryStrategyVersion
|
||||
);
|
||||
|
||||
?.queryStrategy(esSavedObjectClient, queryStrategyVersion);
|
||||
const query = getESQueryHostMetadataByID(id, queryStrategy!);
|
||||
const hostResult = queryStrategy!.queryResponseToHostResult(
|
||||
await metadataRequestContext.requestHandlerContext.core.elasticsearch.legacy.client.callAsCurrentUser(
|
||||
'search',
|
||||
query
|
||||
)
|
||||
);
|
||||
|
||||
const response = await esClient.asCurrentUser.search<HostMetadata>(query);
|
||||
|
||||
const hostResult = queryStrategy!.queryResponseToHostResult(response.body);
|
||||
|
||||
const hostMetadata = hostResult.result;
|
||||
if (!hostMetadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const agent = await findAgent(metadataRequestContext, hostMetadata);
|
||||
return { metadata: hostMetadata, query_strategy_version: hostResult.queryStrategyVersion };
|
||||
}
|
||||
|
||||
export async function getHostData(
|
||||
metadataRequestContext: MetadataRequestContext,
|
||||
id: string,
|
||||
queryStrategyVersion?: MetadataQueryStrategyVersions
|
||||
): Promise<HostInfo | undefined> {
|
||||
if (!metadataRequestContext.savedObjectsClient) {
|
||||
throw Boom.badRequest('savedObjectsClient not found');
|
||||
}
|
||||
|
||||
if (
|
||||
!metadataRequestContext.esClient &&
|
||||
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
|
||||
) {
|
||||
throw Boom.badRequest('esClient not found');
|
||||
}
|
||||
|
||||
const hostResult = await getHostMetaData(metadataRequestContext, id, queryStrategyVersion);
|
||||
|
||||
if (!hostResult) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const agent = await findAgent(metadataRequestContext, hostResult.metadata);
|
||||
|
||||
if (agent && !agent.active) {
|
||||
throw Boom.badRequest('the requested endpoint is unenrolled');
|
||||
}
|
||||
|
||||
const metadata = await enrichHostMetadata(
|
||||
hostMetadata,
|
||||
hostResult.metadata,
|
||||
metadataRequestContext,
|
||||
hostResult.queryStrategyVersion
|
||||
hostResult.query_strategy_version
|
||||
);
|
||||
return { ...metadata, query_strategy_version: hostResult.queryStrategyVersion };
|
||||
|
||||
return { ...metadata, query_strategy_version: hostResult.query_strategy_version };
|
||||
}
|
||||
|
||||
async function findAgent(
|
||||
|
@ -207,12 +265,20 @@ async function findAgent(
|
|||
hostMetadata: HostMetadata
|
||||
): Promise<Agent | undefined> {
|
||||
try {
|
||||
if (
|
||||
!metadataRequestContext.esClient &&
|
||||
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
|
||||
) {
|
||||
throw new Error('esClient not found');
|
||||
}
|
||||
|
||||
const esClient = (metadataRequestContext?.esClient ??
|
||||
metadataRequestContext.requestHandlerContext?.core.elasticsearch
|
||||
.client) as IScopedClusterClient;
|
||||
|
||||
return await metadataRequestContext.endpointAppContextService
|
||||
?.getAgentService()
|
||||
?.getAgent(
|
||||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
hostMetadata.elastic.agent.id
|
||||
);
|
||||
?.getAgent(esClient.asCurrentUser, hostMetadata.elastic.agent.id);
|
||||
} catch (e) {
|
||||
if (e instanceof AgentNotFoundError) {
|
||||
metadataRequestContext.logger.warn(
|
||||
|
@ -232,7 +298,7 @@ export async function mapToHostResultList(
|
|||
metadataRequestContext: MetadataRequestContext
|
||||
): Promise<HostResultList> {
|
||||
const totalNumberOfHosts = hostListQueryResult.resultLength;
|
||||
if (hostListQueryResult.resultList.length > 0) {
|
||||
if ((hostListQueryResult.resultList?.length ?? 0) > 0) {
|
||||
return {
|
||||
request_page_size: queryParams.size,
|
||||
request_page_index: queryParams.from,
|
||||
|
@ -267,6 +333,35 @@ export async function enrichHostMetadata(
|
|||
let hostStatus = HostStatus.UNHEALTHY;
|
||||
let elasticAgentId = hostMetadata?.elastic?.agent?.id;
|
||||
const log = metadataRequestContext.logger;
|
||||
|
||||
try {
|
||||
if (
|
||||
!metadataRequestContext.esClient &&
|
||||
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
|
||||
) {
|
||||
throw new Error('esClient not found');
|
||||
}
|
||||
|
||||
if (
|
||||
!metadataRequestContext.savedObjectsClient &&
|
||||
!metadataRequestContext.requestHandlerContext?.core.savedObjects
|
||||
) {
|
||||
throw new Error('esSavedObjectClient not found');
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const esClient = (metadataRequestContext?.esClient ??
|
||||
metadataRequestContext.requestHandlerContext?.core.elasticsearch
|
||||
.client) as IScopedClusterClient;
|
||||
|
||||
const esSavedObjectClient =
|
||||
metadataRequestContext?.savedObjectsClient ??
|
||||
(metadataRequestContext.requestHandlerContext?.core.savedObjects
|
||||
.client as SavedObjectsClientContract);
|
||||
|
||||
try {
|
||||
/**
|
||||
* Get agent status by elastic agent id if available or use the endpoint-agent id.
|
||||
|
@ -279,10 +374,7 @@ export async function enrichHostMetadata(
|
|||
|
||||
const status = await metadataRequestContext.endpointAppContextService
|
||||
?.getAgentService()
|
||||
?.getAgentStatusById(
|
||||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
elasticAgentId
|
||||
);
|
||||
?.getAgentStatusById(esClient.asCurrentUser, elasticAgentId);
|
||||
hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.UNHEALTHY;
|
||||
} catch (e) {
|
||||
if (e instanceof AgentNotFoundError) {
|
||||
|
@ -297,17 +389,10 @@ export async function enrichHostMetadata(
|
|||
try {
|
||||
const agent = await metadataRequestContext.endpointAppContextService
|
||||
?.getAgentService()
|
||||
?.getAgent(
|
||||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
elasticAgentId
|
||||
);
|
||||
?.getAgent(esClient.asCurrentUser, elasticAgentId);
|
||||
const agentPolicy = await metadataRequestContext.endpointAppContextService
|
||||
.getAgentPolicyService()
|
||||
?.get(
|
||||
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
|
||||
agent?.policy_id!,
|
||||
true
|
||||
);
|
||||
?.get(esSavedObjectClient, agent?.policy_id!, true);
|
||||
const endpointPolicy = ((agentPolicy?.package_policies || []) as PackagePolicy[]).find(
|
||||
(policy: PackagePolicy) => policy.package?.name === 'endpoint'
|
||||
);
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
ILegacyClusterClient,
|
||||
ILegacyScopedClusterClient,
|
||||
KibanaResponseFactory,
|
||||
RequestHandler,
|
||||
RouteConfig,
|
||||
|
@ -50,12 +48,17 @@ import { PackageService } from '../../../../../fleet/server/services';
|
|||
import { metadataTransformPrefix } from '../../../../common/endpoint/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../types';
|
||||
import { PackagePolicyServiceInterface } from '../../../../../fleet/server';
|
||||
import {
|
||||
ClusterClientMock,
|
||||
ScopedClusterClientMock,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../../src/core/server/elasticsearch/client/mocks';
|
||||
|
||||
describe('test endpoint route', () => {
|
||||
let routerMock: jest.Mocked<SecuritySolutionPluginRouter>;
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
let mockClusterClient: jest.Mocked<ILegacyClusterClient>;
|
||||
let mockScopedClient: jest.Mocked<ILegacyScopedClusterClient>;
|
||||
let mockClusterClient: ClusterClientMock;
|
||||
let mockScopedClient: ScopedClusterClientMock;
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockPackageService: jest.Mocked<PackageService>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -76,8 +79,8 @@ describe('test endpoint route', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockScopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockClusterClient = elasticsearchServiceMock.createLegacyClusterClient() as jest.Mocked<ILegacyClusterClient>;
|
||||
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
mockClusterClient = elasticsearchServiceMock.createClusterClient();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
|
||||
routerMock = httpServiceMock.createRouter();
|
||||
|
@ -119,7 +122,9 @@ describe('test endpoint route', () => {
|
|||
it('test find the latest of all endpoints', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest({});
|
||||
const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
)!;
|
||||
|
@ -131,7 +136,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -157,7 +162,9 @@ describe('test endpoint route', () => {
|
|||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: true,
|
||||
} as unknown) as Agent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
|
@ -169,7 +176,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -214,7 +221,9 @@ describe('test endpoint route', () => {
|
|||
it('test find the latest of all endpoints', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest({});
|
||||
const response = createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
)!;
|
||||
|
@ -226,7 +235,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -258,8 +267,10 @@ describe('test endpoint route', () => {
|
|||
|
||||
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
|
||||
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata()))
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
body: createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata()),
|
||||
})
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
|
@ -270,10 +281,10 @@ describe('test endpoint route', () => {
|
|||
mockRequest,
|
||||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must_not
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool
|
||||
.must_not
|
||||
).toContainEqual({
|
||||
terms: {
|
||||
'elastic.agent.id': [
|
||||
|
@ -315,8 +326,10 @@ describe('test endpoint route', () => {
|
|||
|
||||
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
|
||||
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata()))
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
body: createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata()),
|
||||
})
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
|
@ -328,10 +341,10 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toBeCalled();
|
||||
expect(mockScopedClient.asCurrentUser.search).toBeCalled();
|
||||
expect(
|
||||
// KQL filter to be passed through
|
||||
mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must
|
||||
).toContainEqual({
|
||||
bool: {
|
||||
must_not: {
|
||||
|
@ -349,7 +362,7 @@ describe('test endpoint route', () => {
|
|||
},
|
||||
});
|
||||
expect(
|
||||
mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must
|
||||
).toContainEqual({
|
||||
bool: {
|
||||
must_not: [
|
||||
|
@ -393,8 +406,8 @@ describe('test endpoint route', () => {
|
|||
it('should return 404 on no results', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } });
|
||||
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createV2SearchResponse())
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: createV2SearchResponse() })
|
||||
);
|
||||
|
||||
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
|
||||
|
@ -411,7 +424,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -431,7 +444,9 @@ describe('test endpoint route', () => {
|
|||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: true,
|
||||
} as unknown) as Agent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
|
@ -443,7 +458,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -470,7 +485,9 @@ describe('test endpoint route', () => {
|
|||
SavedObjectsErrorHelpers.createGenericNotFoundError();
|
||||
});
|
||||
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
|
@ -482,7 +499,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -503,7 +520,9 @@ describe('test endpoint route', () => {
|
|||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: true,
|
||||
} as unknown) as Agent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_ROUTE}`)
|
||||
|
@ -515,7 +534,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -531,7 +550,9 @@ describe('test endpoint route', () => {
|
|||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { id: response.hits.hits[0]._id },
|
||||
});
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: false,
|
||||
} as unknown) as Agent);
|
||||
|
@ -546,7 +567,7 @@ describe('test endpoint route', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.customError).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,14 +6,17 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
ILegacyClusterClient,
|
||||
ILegacyScopedClusterClient,
|
||||
KibanaResponseFactory,
|
||||
RequestHandler,
|
||||
RouteConfig,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server/';
|
||||
SavedObjectsErrorHelpers,
|
||||
} from '../../../../../../../src/core/server';
|
||||
import {
|
||||
ClusterClientMock,
|
||||
ScopedClusterClientMock,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../../src/core/server/elasticsearch/client/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
|
@ -49,8 +52,8 @@ import { PackagePolicyServiceInterface } from '../../../../../fleet/server';
|
|||
describe('test endpoint route v1', () => {
|
||||
let routerMock: jest.Mocked<SecuritySolutionPluginRouter>;
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
let mockClusterClient: jest.Mocked<ILegacyClusterClient>;
|
||||
let mockScopedClient: jest.Mocked<ILegacyScopedClusterClient>;
|
||||
let mockClusterClient: ClusterClientMock;
|
||||
let mockScopedClient: ScopedClusterClientMock;
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockPackageService: jest.Mocked<PackageService>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -71,8 +74,8 @@ describe('test endpoint route v1', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockClusterClient = elasticsearchServiceMock.createLegacyClusterClient() as jest.Mocked<ILegacyClusterClient>;
|
||||
mockScopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockClusterClient = elasticsearchServiceMock.createClusterClient();
|
||||
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
|
||||
routerMock = httpServiceMock.createRouter();
|
||||
|
@ -110,7 +113,9 @@ describe('test endpoint route v1', () => {
|
|||
it('test find the latest of all endpoints', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest({});
|
||||
const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`)
|
||||
)!;
|
||||
|
@ -122,7 +127,7 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] });
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList;
|
||||
|
@ -151,8 +156,10 @@ describe('test endpoint route v1', () => {
|
|||
|
||||
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
|
||||
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()))
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
body: createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()),
|
||||
})
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`)
|
||||
|
@ -164,9 +171,10 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must_not
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool
|
||||
.must_not
|
||||
).toContainEqual({
|
||||
terms: {
|
||||
'elastic.agent.id': [
|
||||
|
@ -205,8 +213,10 @@ describe('test endpoint route v1', () => {
|
|||
|
||||
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
|
||||
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()))
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
body: createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()),
|
||||
})
|
||||
);
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`)
|
||||
|
@ -218,10 +228,10 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toBeCalled();
|
||||
expect(mockScopedClient.asCurrentUser.search).toBeCalled();
|
||||
// needs to have the KQL filter passed through
|
||||
expect(
|
||||
mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must
|
||||
).toContainEqual({
|
||||
bool: {
|
||||
must_not: {
|
||||
|
@ -240,7 +250,7 @@ describe('test endpoint route v1', () => {
|
|||
});
|
||||
// and unenrolled should be filtered out.
|
||||
expect(
|
||||
mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must
|
||||
).toContainEqual({
|
||||
bool: {
|
||||
must_not: [
|
||||
|
@ -281,8 +291,8 @@ describe('test endpoint route v1', () => {
|
|||
it('should return 404 on no results', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } });
|
||||
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createV1SearchResponse())
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: createV1SearchResponse() })
|
||||
);
|
||||
|
||||
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
|
||||
|
@ -299,7 +309,7 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -319,7 +329,9 @@ describe('test endpoint route v1', () => {
|
|||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: true,
|
||||
} as unknown) as Agent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`)
|
||||
|
@ -331,7 +343,7 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -357,7 +369,9 @@ describe('test endpoint route v1', () => {
|
|||
SavedObjectsErrorHelpers.createGenericNotFoundError();
|
||||
});
|
||||
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`)
|
||||
|
@ -369,7 +383,7 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -390,7 +404,9 @@ describe('test endpoint route v1', () => {
|
|||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: true,
|
||||
} as unknown) as Agent);
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
|
||||
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`)
|
||||
|
@ -402,7 +418,7 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(routeConfig.options).toEqual({
|
||||
authRequired: true,
|
||||
tags: ['access:securitySolution'],
|
||||
|
@ -418,7 +434,9 @@ describe('test endpoint route v1', () => {
|
|||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { id: response.hits.hits[0]._id },
|
||||
});
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
mockAgentService.getAgent = jest.fn().mockReturnValue(({
|
||||
active: false,
|
||||
} as unknown) as Agent);
|
||||
|
@ -433,7 +451,7 @@ describe('test endpoint route v1', () => {
|
|||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
|
||||
expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.customError).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__
|
|||
import { metadataCurrentIndexPattern } from '../../../../common/endpoint/constants';
|
||||
import { parseExperimentalConfigValue } from '../../../../common/experimental_features';
|
||||
import { metadataQueryStrategyV2 } from './support/query_strategies';
|
||||
import { get } from 'lodash';
|
||||
|
||||
describe('query builder', () => {
|
||||
describe('MetadataListESQuery', () => {
|
||||
|
@ -204,7 +205,7 @@ describe('query builder', () => {
|
|||
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
|
||||
const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2());
|
||||
|
||||
expect(query.body.query.bool.filter[0].bool.should).toContainEqual({
|
||||
expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({
|
||||
term: { 'agent.id': mockID },
|
||||
});
|
||||
});
|
||||
|
@ -213,7 +214,7 @@ describe('query builder', () => {
|
|||
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
|
||||
const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2());
|
||||
|
||||
expect(query.body.query.bool.filter[0].bool.should).toContainEqual({
|
||||
expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({
|
||||
term: { 'HostDetails.agent.id': mockID },
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { KibanaRequest } from 'kibana/server';
|
||||
import { SearchRequest, SortContainer } from '@elastic/elasticsearch/api/types';
|
||||
import { KibanaRequest } from '../../../../../../../src/core/server';
|
||||
import { esKuery } from '../../../../../../../src/plugins/data/server';
|
||||
import { EndpointAppContext, MetadataQueryStrategy } from '../../types';
|
||||
|
||||
|
@ -19,7 +20,7 @@ export interface QueryBuilderOptions {
|
|||
// using unmapped_type avoids errors when the given field doesn't exist, and sets to the 0-value for that type
|
||||
// effectively ignoring it
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html#_ignoring_unmapped_fields
|
||||
const MetadataSortMethod = [
|
||||
const MetadataSortMethod: SortContainer[] = [
|
||||
{
|
||||
'event.created': {
|
||||
order: 'desc',
|
||||
|
@ -146,7 +147,7 @@ function buildQueryBody(
|
|||
export function getESQueryHostMetadataByID(
|
||||
agentID: string,
|
||||
metadataQueryStrategy: MetadataQueryStrategy
|
||||
) {
|
||||
): SearchRequest {
|
||||
return {
|
||||
body: {
|
||||
query: {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__
|
|||
import { metadataIndexPattern } from '../../../../common/endpoint/constants';
|
||||
import { parseExperimentalConfigValue } from '../../../../common/experimental_features';
|
||||
import { metadataQueryStrategyV1 } from './support/query_strategies';
|
||||
import { get } from 'lodash';
|
||||
|
||||
describe('query builder v1', () => {
|
||||
describe('MetadataListESQuery', () => {
|
||||
|
@ -179,7 +180,7 @@ describe('query builder v1', () => {
|
|||
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
|
||||
const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV1());
|
||||
|
||||
expect(query.body.query.bool.filter[0].bool.should).toContainEqual({
|
||||
expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({
|
||||
term: { 'agent.id': mockID },
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { SearchResponse } from '@elastic/elasticsearch/api/types';
|
||||
import {
|
||||
metadataCurrentIndexPattern,
|
||||
metadataIndexPattern,
|
||||
|
@ -13,10 +13,6 @@ import {
|
|||
import { HostMetadata, MetadataQueryStrategyVersions } from '../../../../../common/endpoint/types';
|
||||
import { HostListQueryResult, HostQueryResult, MetadataQueryStrategy } from '../../../types';
|
||||
|
||||
interface HitSource {
|
||||
_source: HostMetadata;
|
||||
}
|
||||
|
||||
export function metadataQueryStrategyV1(): MetadataQueryStrategy {
|
||||
return {
|
||||
index: metadataIndexPattern,
|
||||
|
@ -42,11 +38,13 @@ export function metadataQueryStrategyV1(): MetadataQueryStrategy {
|
|||
): HostListQueryResult => {
|
||||
const response = searchResponse as SearchResponse<HostMetadata>;
|
||||
return {
|
||||
resultLength: response?.aggregations?.total?.value || 0,
|
||||
resultLength:
|
||||
((response?.aggregations?.total as unknown) as { value?: number; relation: string })
|
||||
?.value || 0,
|
||||
resultList: response.hits.hits
|
||||
.map((hit) => hit.inner_hits.most_recent.hits.hits)
|
||||
.flatMap((data) => data as HitSource)
|
||||
.map((entry) => entry._source),
|
||||
.map((hit) => hit.inner_hits?.most_recent.hits.hits)
|
||||
.flatMap((data) => data)
|
||||
.map((entry) => (entry?._source ?? {}) as HostMetadata),
|
||||
queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_1,
|
||||
};
|
||||
},
|
||||
|
@ -75,7 +73,7 @@ export function metadataQueryStrategyV2(): MetadataQueryStrategy {
|
|||
>;
|
||||
const list =
|
||||
response.hits.hits.length > 0
|
||||
? response.hits.hits.map((entry) => stripHostDetails(entry._source))
|
||||
? response.hits.hits.map((entry) => stripHostDetails(entry?._source as HostMetadata))
|
||||
: [];
|
||||
|
||||
return {
|
||||
|
@ -95,7 +93,7 @@ export function metadataQueryStrategyV2(): MetadataQueryStrategy {
|
|||
resultLength: response.hits.hits.length,
|
||||
result:
|
||||
response.hits.hits.length > 0
|
||||
? stripHostDetails(response.hits.hits[0]._source)
|
||||
? stripHostDetails(response.hits.hits[0]._source as HostMetadata)
|
||||
: undefined,
|
||||
queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2,
|
||||
};
|
||||
|
|
|
@ -13,10 +13,9 @@ import {
|
|||
import { createMockAgentService } from '../../../../../fleet/server/mocks';
|
||||
import { getHostPolicyResponseHandler, getAgentPolicySummaryHandler } from './handlers';
|
||||
import {
|
||||
ILegacyScopedClusterClient,
|
||||
KibanaResponseFactory,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
} from '../../../../../../../src/core/server';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
|
@ -30,16 +29,19 @@ import { parseExperimentalConfigValue } from '../../../../common/experimental_fe
|
|||
import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
|
||||
import { Agent } from '../../../../../fleet/common/types/models';
|
||||
import { AgentService } from '../../../../../fleet/server/services';
|
||||
import { get } from 'lodash';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ScopedClusterClientMock } from '../../../../../../../src/core/server/elasticsearch/client/mocks';
|
||||
|
||||
describe('test policy response handler', () => {
|
||||
let endpointAppContextService: EndpointAppContextService;
|
||||
let mockScopedClient: jest.Mocked<ILegacyScopedClusterClient>;
|
||||
let mockScopedClient: ScopedClusterClientMock;
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
|
||||
describe('test policy response handler', () => {
|
||||
beforeEach(() => {
|
||||
mockScopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
endpointAppContextService = new EndpointAppContextService();
|
||||
|
@ -52,7 +54,9 @@ describe('test policy response handler', () => {
|
|||
const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse());
|
||||
const hostPolicyResponseHandler = getHostPolicyResponseHandler();
|
||||
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response));
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: response })
|
||||
);
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { agentId: 'id' },
|
||||
});
|
||||
|
@ -65,14 +69,16 @@ describe('test policy response handler', () => {
|
|||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as GetHostPolicyResponse;
|
||||
expect(result.policy_response.agent.id).toEqual(response.hits.hits[0]._source.agent.id);
|
||||
expect(result.policy_response.agent.id).toEqual(
|
||||
get(response, 'hits.hits.0._source.agent.id')
|
||||
);
|
||||
});
|
||||
|
||||
it('should return not found when there is no response policy for host', async () => {
|
||||
const hostPolicyResponseHandler = getHostPolicyResponseHandler();
|
||||
|
||||
mockScopedClient.callAsCurrentUser.mockImplementationOnce(() =>
|
||||
Promise.resolve(createSearchResponse())
|
||||
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({ body: createSearchResponse() })
|
||||
);
|
||||
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
|
@ -109,7 +115,7 @@ describe('test policy response handler', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockScopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient();
|
||||
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
endpointAppContextService = new EndpointAppContextService();
|
||||
|
|
|
@ -25,7 +25,7 @@ export const getHostPolicyResponseHandler = function (): RequestHandler<
|
|||
const doc = await getPolicyResponseByAgentId(
|
||||
policyIndexPattern,
|
||||
request.query.agentId,
|
||||
context.core.elasticsearch.legacy.client
|
||||
context.core.elasticsearch.client
|
||||
);
|
||||
|
||||
if (doc) {
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('test policy query', () => {
|
|||
it('queries for the correct host', async () => {
|
||||
const agentId = 'f757d3c0-e874-11ea-9ad9-015510b487f4';
|
||||
const query = getESQueryPolicyResponseByAgentID(agentId, 'anyindex');
|
||||
expect(query.body.query.bool.filter.term).toEqual({ 'agent.id': agentId });
|
||||
expect(query.body?.query?.bool?.filter).toEqual({ term: { 'agent.id': agentId } });
|
||||
});
|
||||
|
||||
it('filters out initial policy by ID', async () => {
|
||||
|
@ -32,8 +32,10 @@ describe('test policy query', () => {
|
|||
'f757d3c0-e874-11ea-9ad9-015510b487f4',
|
||||
'anyindex'
|
||||
);
|
||||
expect(query.body.query.bool.must_not.term).toEqual({
|
||||
'Endpoint.policy.applied.id': '00000000-0000-0000-0000-000000000000',
|
||||
expect(query.body?.query?.bool?.must_not).toEqual({
|
||||
term: {
|
||||
'Endpoint.policy.applied.id': '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,18 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
ILegacyScopedClusterClient,
|
||||
IScopedClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
} from '../../../../../../../src/core/server';
|
||||
import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types';
|
||||
import { INITIAL_POLICY_ID } from './index';
|
||||
import { Agent } from '../../../../../fleet/common/types/models';
|
||||
import { EndpointAppContext } from '../../types';
|
||||
import { ISearchRequestParams } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
export function getESQueryPolicyResponseByAgentID(agentID: string, index: string) {
|
||||
export const getESQueryPolicyResponseByAgentID = (
|
||||
agentID: string,
|
||||
index: string
|
||||
): ISearchRequestParams => {
|
||||
return {
|
||||
body: {
|
||||
query: {
|
||||
|
@ -44,26 +47,23 @@ export function getESQueryPolicyResponseByAgentID(agentID: string, index: string
|
|||
},
|
||||
index,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export async function getPolicyResponseByAgentId(
|
||||
index: string,
|
||||
agentID: string,
|
||||
dataClient: ILegacyScopedClusterClient
|
||||
dataClient: IScopedClusterClient
|
||||
): Promise<GetHostPolicyResponse | undefined> {
|
||||
const query = getESQueryPolicyResponseByAgentID(agentID, index);
|
||||
const response = (await dataClient.callAsCurrentUser(
|
||||
'search',
|
||||
query
|
||||
)) as SearchResponse<HostPolicyResponse>;
|
||||
const response = await dataClient.asCurrentUser.search<HostPolicyResponse>(query);
|
||||
|
||||
if (response.hits.hits.length === 0) {
|
||||
return undefined;
|
||||
if (response.body.hits.hits.length > 0 && response.body.hits.hits[0]._source != null) {
|
||||
return {
|
||||
policy_response: response.body.hits.hits[0]._source,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
policy_response: response.hits.hits[0]._source,
|
||||
};
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const transformAgentVersionMap = (versionMap: Map<string, number>): { [key: string]: number } => {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import { LoggerFactory } from 'kibana/server';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
|
||||
import { SearchResponse } from '@elastic/elasticsearch/api/types';
|
||||
import { ConfigType } from '../config';
|
||||
import { EndpointAppContextService } from './endpoint_app_context_services';
|
||||
import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
|
||||
|
|
|
@ -305,7 +305,10 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
compose(core, plugins, endpointContext);
|
||||
|
||||
core.getStartServices().then(([_, depsStart]) => {
|
||||
const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider(depsStart.data);
|
||||
const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider(
|
||||
depsStart.data,
|
||||
endpointContext
|
||||
);
|
||||
const securitySolutionTimelineSearchStrategy = securitySolutionTimelineSearchStrategyProvider(
|
||||
depsStart.data
|
||||
);
|
||||
|
|
|
@ -58,7 +58,6 @@ export const authentications: SecuritySolutionFactory<HostsQueries.authenticatio
|
|||
dsl: [inspectStringifyObject(buildAuthenticationQuery(options))],
|
||||
};
|
||||
const showMorePagesIndicator = totalCount > fakeTotalCount;
|
||||
|
||||
return {
|
||||
...response,
|
||||
inspect,
|
||||
|
|
|
@ -1370,6 +1370,20 @@ export const formattedSearchStrategyResponse = {
|
|||
terms: { field: 'cloud.region', size: 10, order: { timestamp: 'desc' } },
|
||||
aggs: { timestamp: { max: { field: '@timestamp' } } },
|
||||
},
|
||||
endpoint_id: {
|
||||
filter: {
|
||||
term: {
|
||||
'agent.type': 'endpoint',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
value: {
|
||||
terms: {
|
||||
field: 'agent.id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
|
@ -1413,6 +1427,20 @@ export const expectedDsl = {
|
|||
track_total_hits: false,
|
||||
body: {
|
||||
aggregations: {
|
||||
endpoint_id: {
|
||||
filter: {
|
||||
term: {
|
||||
'agent.type': 'endpoint',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
value: {
|
||||
terms: {
|
||||
field: 'agent.id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
host_architecture: {
|
||||
terms: {
|
||||
field: 'host.architecture',
|
||||
|
|
|
@ -7,16 +7,23 @@
|
|||
|
||||
import { set } from '@elastic/safer-lodash-set/fp';
|
||||
import { get, has, head } from 'lodash/fp';
|
||||
import {
|
||||
IScopedClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../../../../../../src/core/server';
|
||||
import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields';
|
||||
import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array';
|
||||
import { Direction } from '../../../../../../common/search_strategy/common';
|
||||
import {
|
||||
AggregationRequest,
|
||||
EndpointFields,
|
||||
HostAggEsItem,
|
||||
HostBuckets,
|
||||
HostItem,
|
||||
HostValue,
|
||||
} from '../../../../../../common/search_strategy/security_solution/hosts';
|
||||
import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array';
|
||||
import { getHostMetaData } from '../../../../../endpoint/routes/metadata/handlers';
|
||||
import { EndpointAppContext } from '../../../../../endpoint/types';
|
||||
|
||||
export const HOST_FIELDS = [
|
||||
'_id',
|
||||
|
@ -38,6 +45,8 @@ export const HOST_FIELDS = [
|
|||
'endpoint.endpointPolicy',
|
||||
'endpoint.policyStatus',
|
||||
'endpoint.sensorVersion',
|
||||
'agent.type',
|
||||
'endpoint.id',
|
||||
];
|
||||
|
||||
export const buildFieldsTermAggregation = (esFields: readonly string[]): AggregationRequest =>
|
||||
|
@ -99,8 +108,8 @@ const getTermsAggregationTypeFromField = (field: string): AggregationRequest =>
|
|||
};
|
||||
};
|
||||
|
||||
export const formatHostItem = (bucket: HostAggEsItem): HostItem =>
|
||||
HOST_FIELDS.reduce<HostItem>((flattenedFields, fieldName) => {
|
||||
export const formatHostItem = (bucket: HostAggEsItem): HostItem => {
|
||||
return HOST_FIELDS.reduce<HostItem>((flattenedFields, fieldName) => {
|
||||
const fieldValue = getHostFieldValue(fieldName, bucket);
|
||||
if (fieldValue != null) {
|
||||
if (fieldName === '_id') {
|
||||
|
@ -114,11 +123,13 @@ export const formatHostItem = (bucket: HostAggEsItem): HostItem =>
|
|||
}
|
||||
return flattenedFields;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | string[] | null => {
|
||||
const aggField = hostFieldsMap[fieldName]
|
||||
? hostFieldsMap[fieldName].replace(/\./g, '_')
|
||||
: fieldName.replace(/\./g, '_');
|
||||
|
||||
if (
|
||||
[
|
||||
'host.ip',
|
||||
|
@ -134,10 +145,7 @@ const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | s
|
|||
return data.buckets.map((obj) => obj.key);
|
||||
} else if (has(`${aggField}.buckets`, bucket)) {
|
||||
return getFirstItem(get(`${aggField}`, bucket));
|
||||
} else if (has(aggField, bucket)) {
|
||||
const valueObj: HostValue = get(aggField, bucket);
|
||||
return valueObj.value_as_string;
|
||||
} else if (['host.name', 'host.os.name', 'host.os.version'].includes(fieldName)) {
|
||||
} else if (['host.name', 'host.os.name', 'host.os.version', 'endpoint.id'].includes(fieldName)) {
|
||||
switch (fieldName) {
|
||||
case 'host.name':
|
||||
return get('key', bucket) || null;
|
||||
|
@ -145,7 +153,12 @@ const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | s
|
|||
return get('os.hits.hits[0]._source.host.os.name', bucket) || null;
|
||||
case 'host.os.version':
|
||||
return get('os.hits.hits[0]._source.host.os.version', bucket) || null;
|
||||
case 'endpoint.id':
|
||||
return get('endpoint_id.value.buckets[0].key', bucket) || null;
|
||||
}
|
||||
} else if (has(aggField, bucket)) {
|
||||
const valueObj: HostValue = get(aggField, bucket);
|
||||
return valueObj.value_as_string;
|
||||
} else if (aggField === '_id') {
|
||||
const hostName = get(`host_name`, bucket);
|
||||
return hostName ? getFirstItem(hostName) : null;
|
||||
|
@ -160,3 +173,42 @@ const getFirstItem = (data: HostBuckets): string | null => {
|
|||
}
|
||||
return firstItem.key;
|
||||
};
|
||||
|
||||
export const getHostEndpoint = async (
|
||||
id: string | null,
|
||||
deps: {
|
||||
esClient: IScopedClusterClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
endpointContext: EndpointAppContext;
|
||||
}
|
||||
): Promise<EndpointFields | null> => {
|
||||
const { esClient, endpointContext, savedObjectsClient } = deps;
|
||||
const logger = endpointContext.logFactory.get('metadata');
|
||||
try {
|
||||
const agentService = endpointContext.service.getAgentService();
|
||||
if (agentService === undefined) {
|
||||
throw new Error('agentService not available');
|
||||
}
|
||||
const metadataRequestContext = {
|
||||
esClient,
|
||||
endpointAppContextService: endpointContext.service,
|
||||
logger,
|
||||
savedObjectsClient,
|
||||
};
|
||||
const endpointData =
|
||||
id != null && metadataRequestContext.endpointAppContextService.getAgentService() != null
|
||||
? await getHostMetaData(metadataRequestContext, id, undefined)
|
||||
: null;
|
||||
|
||||
return endpointData != null && endpointData.metadata
|
||||
? {
|
||||
endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name,
|
||||
policyStatus: endpointData.metadata.Endpoint.policy.applied.status,
|
||||
sensorVersion: endpointData.metadata.agent.version,
|
||||
}
|
||||
: null;
|
||||
} catch (err) {
|
||||
logger.warn(JSON.stringify(err, null, 2));
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,6 +12,32 @@ import {
|
|||
mockSearchStrategyResponse,
|
||||
formattedSearchStrategyResponse,
|
||||
} from './__mocks__';
|
||||
import {
|
||||
IScopedClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../../../../../../src/core/server';
|
||||
import { EndpointAppContext } from '../../../../../endpoint/types';
|
||||
import { EndpointAppContextService } from '../../../../../endpoint/endpoint_app_context_services';
|
||||
|
||||
const mockDeps = {
|
||||
esClient: {} as IScopedClusterClient,
|
||||
savedObjectsClient: {} as SavedObjectsClientContract,
|
||||
endpointContext: {
|
||||
logFactory: {
|
||||
get: jest.fn().mockReturnValue({
|
||||
warn: jest.fn(),
|
||||
}),
|
||||
},
|
||||
config: jest.fn().mockResolvedValue({}),
|
||||
experimentalFeatures: {
|
||||
trustedAppsByPolicyEnabled: false,
|
||||
metricsEntitiesEnabled: false,
|
||||
eventFilteringEnabled: false,
|
||||
hostIsolationEnabled: false,
|
||||
},
|
||||
service: {} as EndpointAppContextService,
|
||||
} as EndpointAppContext,
|
||||
};
|
||||
|
||||
describe('hostDetails search strategy', () => {
|
||||
const buildHostDetailsQuery = jest.spyOn(buildQuery, 'buildHostDetailsQuery');
|
||||
|
@ -29,7 +55,7 @@ describe('hostDetails search strategy', () => {
|
|||
|
||||
describe('parse', () => {
|
||||
test('should parse data correctly', async () => {
|
||||
const result = await hostDetails.parse(mockOptions, mockSearchStrategyResponse);
|
||||
const result = await hostDetails.parse(mockOptions, mockSearchStrategyResponse, mockDeps);
|
||||
expect(result).toMatchObject(formattedSearchStrategyResponse);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,28 +10,58 @@ import { get } from 'lodash/fp';
|
|||
import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
|
||||
import {
|
||||
HostAggEsData,
|
||||
HostAggEsItem,
|
||||
HostDetailsStrategyResponse,
|
||||
HostsQueries,
|
||||
HostDetailsRequestOptions,
|
||||
EndpointFields,
|
||||
} from '../../../../../../common/search_strategy/security_solution/hosts';
|
||||
|
||||
import { inspectStringifyObject } from '../../../../../utils/build_query';
|
||||
import { SecuritySolutionFactory } from '../../types';
|
||||
import { buildHostDetailsQuery } from './query.host_details.dsl';
|
||||
import { formatHostItem } from './helpers';
|
||||
import { formatHostItem, getHostEndpoint } from './helpers';
|
||||
import { EndpointAppContext } from '../../../../../endpoint/types';
|
||||
import {
|
||||
IScopedClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../../../../../../src/core/server';
|
||||
|
||||
export const hostDetails: SecuritySolutionFactory<HostsQueries.details> = {
|
||||
buildDsl: (options: HostDetailsRequestOptions) => buildHostDetailsQuery(options),
|
||||
parse: async (
|
||||
options: HostDetailsRequestOptions,
|
||||
response: IEsSearchResponse<HostAggEsData>
|
||||
response: IEsSearchResponse<HostAggEsData>,
|
||||
deps?: {
|
||||
esClient: IScopedClusterClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
endpointContext: EndpointAppContext;
|
||||
}
|
||||
): Promise<HostDetailsStrategyResponse> => {
|
||||
const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {};
|
||||
const aggregations = get('aggregations', response.rawResponse);
|
||||
|
||||
const inspect = {
|
||||
dsl: [inspectStringifyObject(buildHostDetailsQuery(options))],
|
||||
};
|
||||
|
||||
if (aggregations == null) {
|
||||
return { ...response, inspect, hostDetails: {} };
|
||||
}
|
||||
|
||||
const formattedHostItem = formatHostItem(aggregations);
|
||||
return { ...response, inspect, hostDetails: formattedHostItem };
|
||||
const ident = // endpoint-generated ID, NOT elastic-agent-id
|
||||
formattedHostItem.endpoint && formattedHostItem.endpoint.id
|
||||
? Array.isArray(formattedHostItem.endpoint.id)
|
||||
? formattedHostItem.endpoint.id[0]
|
||||
: formattedHostItem.endpoint.id
|
||||
: null;
|
||||
if (deps == null) {
|
||||
return { ...response, inspect, hostDetails: { ...formattedHostItem } };
|
||||
}
|
||||
const endpoint: EndpointFields | null = await getHostEndpoint(ident, deps);
|
||||
return {
|
||||
...response,
|
||||
inspect,
|
||||
hostDetails: endpoint != null ? { ...formattedHostItem, endpoint } : formattedHostItem,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -16,7 +16,10 @@ export const buildHostDetailsQuery = ({
|
|||
defaultIndex,
|
||||
timerange: { from, to },
|
||||
}: HostDetailsRequestOptions): ISearchRequestParams => {
|
||||
const esFields = reduceFields(HOST_FIELDS, { ...hostFieldsMap, ...cloudFieldsMap });
|
||||
const esFields = reduceFields(HOST_FIELDS, {
|
||||
...hostFieldsMap,
|
||||
...cloudFieldsMap,
|
||||
});
|
||||
|
||||
const filter = [
|
||||
{ term: { 'host.name': hostName } },
|
||||
|
@ -39,6 +42,20 @@ export const buildHostDetailsQuery = ({
|
|||
body: {
|
||||
aggregations: {
|
||||
...buildFieldsTermAggregation(esFields.filter((field) => !['@timestamp'].includes(field))),
|
||||
endpoint_id: {
|
||||
filter: {
|
||||
term: {
|
||||
'agent.type': 'endpoint',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
value: {
|
||||
terms: {
|
||||
field: 'agent.id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
query: { bool: { filter } },
|
||||
size: 0,
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
IScopedClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../../../../src/core/server';
|
||||
import {
|
||||
IEsSearchResponse,
|
||||
ISearchRequestParams,
|
||||
|
@ -14,11 +18,17 @@ import {
|
|||
StrategyRequestType,
|
||||
StrategyResponseType,
|
||||
} from '../../../../common/search_strategy/security_solution';
|
||||
import { EndpointAppContext } from '../../../endpoint/types';
|
||||
|
||||
export interface SecuritySolutionFactory<T extends FactoryQueryTypes> {
|
||||
buildDsl: (options: StrategyRequestType<T>) => ISearchRequestParams;
|
||||
parse: (
|
||||
options: StrategyRequestType<T>,
|
||||
response: IEsSearchResponse
|
||||
response: IEsSearchResponse,
|
||||
deps?: {
|
||||
esClient: IScopedClusterClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
endpointContext: EndpointAppContext;
|
||||
}
|
||||
) => Promise<StrategyResponseType<T>>;
|
||||
}
|
||||
|
|
|
@ -19,9 +19,11 @@ import {
|
|||
} from '../../../common/search_strategy/security_solution';
|
||||
import { securitySolutionFactory } from './factory';
|
||||
import { SecuritySolutionFactory } from './factory/types';
|
||||
import { EndpointAppContext } from '../../endpoint/types';
|
||||
|
||||
export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTypes>(
|
||||
data: PluginStart
|
||||
data: PluginStart,
|
||||
endpointContext: EndpointAppContext
|
||||
): ISearchStrategy<StrategyRequestType<T>, StrategyResponseType<T>> => {
|
||||
const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY);
|
||||
|
||||
|
@ -42,7 +44,13 @@ export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTyp
|
|||
},
|
||||
};
|
||||
}),
|
||||
mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes))
|
||||
mergeMap((esSearchRes) =>
|
||||
queryFactory.parse(request, esSearchRes, {
|
||||
esClient: deps.esClient,
|
||||
savedObjectsClient: deps.savedObjectsClient,
|
||||
endpointContext,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
cancel: async (id, options, deps) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue