[Fleet] Add scoped AgentService (#119017) (#120140)

Co-authored-by: Paul Tavares <paul.tavares@elastic.co>

Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>
Co-authored-by: Paul Tavares <paul.tavares@elastic.co>
This commit is contained in:
Kibana Machine 2021-12-01 14:18:54 -05:00 committed by GitHub
parent 32cd3f4c20
commit bb960f1b04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 736 additions and 240 deletions

View file

@ -5,5 +5,5 @@
* 2.0.
*/
export const PLUGIN_ID = 'fleet';
export const INTEGRATIONS_PLUGIN_ID = 'integrations';
export const PLUGIN_ID = 'fleet' as const;
export const INTEGRATIONS_PLUGIN_ID = 'integrations' as const;

View file

@ -52,6 +52,7 @@ export class HostedAgentPolicyRestrictionRelatedError extends IngestManagerError
export class FleetSetupError extends IngestManagerError {}
export class GenerateServiceTokenError extends IngestManagerError {}
export class FleetUnauthorizedError extends IngestManagerError {}
export class OutputUnauthorizedError extends IngestManagerError {}

View file

@ -19,6 +19,7 @@ import { FleetPlugin } from './plugin';
export type {
AgentService,
AgentClient,
ESIndexPatternService,
PackageService,
AgentPolicyServiceInterface,
@ -34,8 +35,9 @@ export type {
PutPackagePolicyUpdateCallback,
PostPackagePolicyDeleteCallback,
PostPackagePolicyCreateCallback,
FleetRequestHandlerContext,
} from './types';
export { AgentNotFoundError } from './errors';
export { AgentNotFoundError, FleetUnauthorizedError } from './errors';
export const config: PluginConfigDescriptor = {
exposeToBrowser: {

View file

@ -11,16 +11,19 @@ import {
loggingSystemMock,
savedObjectsServiceMock,
coreMock,
savedObjectsClientMock,
} from '../../../../../src/core/server/mocks';
import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks';
import { licensingMock } from '../../../../plugins/licensing/server/mocks';
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks';
import { securityMock } from '../../../security/server/mocks';
import type { PackagePolicyServiceInterface } from '../services/package_policy';
import type { AgentPolicyServiceInterface, AgentService } from '../services';
import type { AgentPolicyServiceInterface, PackageService } from '../services';
import type { FleetAppContext } from '../plugin';
import { createMockTelemetryEventsSender } from '../telemetry/__mocks__';
import type { FleetAuthz } from '../../common';
import { agentServiceMock } from '../services/agents/agent_service.mock';
import type { FleetRequestHandlerContext } from '../types';
// Export all mocks from artifacts
export * from '../services/artifacts/mocks';
@ -65,10 +68,26 @@ export const createAppContextStartContractMock = (): MockedFleetAppContext => {
};
};
export const createFleetRequestHandlerContextMock = (): jest.Mocked<
FleetRequestHandlerContext['fleet']
> => {
return {
authz: createFleetAuthzMock(),
agentClient: {
asCurrentUser: agentServiceMock.createClient(),
asInternalUser: agentServiceMock.createClient(),
},
epm: {
internalSoClient: savedObjectsClientMock.create(),
},
};
};
function createCoreRequestHandlerContextMock() {
return {
core: coreMock.createRequestHandlerContext(),
licensing: licensingMock.createRequestHandlerContext(),
fleet: createFleetRequestHandlerContextMock(),
};
}
@ -113,33 +132,40 @@ export const createMockAgentPolicyService = (): jest.Mocked<AgentPolicyServiceIn
/**
* Creates a mock AgentService
*/
export const createMockAgentService = (): jest.Mocked<AgentService> => {
export const createMockAgentService = () => agentServiceMock.create();
/**
* Creates a mock AgentClient
*/
export const createMockAgentClient = () => agentServiceMock.createClient();
export const createMockPackageService = (): PackageService => {
return {
getAgentStatusById: jest.fn(),
getAgentStatusForAgentPolicy: jest.fn(),
getAgent: jest.fn(),
listAgents: jest.fn(),
getInstallation: jest.fn(),
ensureInstalledPackage: jest.fn(),
};
};
/**
* Creates mock `authz` object
*/
export const fleetAuthzMock: FleetAuthz = {
fleet: {
all: true,
setup: true,
readEnrollmentTokens: true,
},
integrations: {
readPackageInfo: true,
readInstalledPackages: true,
installPackages: true,
upgradePackages: true,
removePackages: true,
readPackageSettings: true,
writePackageSettings: true,
readIntegrationPolicies: true,
writeIntegrationPolicies: true,
},
export const createFleetAuthzMock = (): FleetAuthz => {
return {
fleet: {
all: true,
setup: true,
readEnrollmentTokens: true,
},
integrations: {
readPackageInfo: true,
readInstalledPackages: true,
installPackages: true,
upgradePackages: true,
removePackages: true,
readPackageSettings: true,
writePackageSettings: true,
readIntegrationPolicies: true,
writeIntegrationPolicies: true,
},
};
};

View file

@ -16,6 +16,7 @@ import type {
SavedObjectsServiceStart,
HttpServiceSetup,
KibanaRequest,
ElasticsearchClient,
} from 'kibana/server';
import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
@ -71,13 +72,8 @@ import {
ESIndexPatternSavedObjectService,
agentPolicyService,
packagePolicyService,
AgentServiceImpl,
} from './services';
import {
getAgentStatusById,
getAgentStatusForAgentPolicy,
getAgentsByKuery,
getAgentById,
} from './services/agents';
import { registerFleetUsageCollector } from './collectors/register';
import { getInstallation, ensureInstalledPackage } from './services/epm/packages';
import { getAuthzFromRequest, RouterWrappers } from './routes/security';
@ -187,6 +183,8 @@ export class FleetPlugin
private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup;
private readonly telemetryEventsSender: TelemetryEventsSender;
private agentService?: AgentService;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config$ = this.initializerContext.config.create<FleetConfigType>();
this.isProductionMode = this.initializerContext.env.mode.prod;
@ -212,7 +210,7 @@ export class FleetPlugin
// TODO: Flesh out privileges
if (deps.features) {
deps.features.registerKibanaFeature({
id: 'fleet',
id: PLUGIN_ID,
name: 'Fleet and Integrations',
category: DEFAULT_APP_CATEGORIES.management,
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
@ -237,7 +235,7 @@ export class FleetPlugin
},
privileges: {
all: {
api: [`fleet-read`, `fleet-all`, `integrations-all`, `integrations-read`],
api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`, `integrations-all`, `integrations-read`],
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
catalogue: ['fleet'],
savedObject: {
@ -247,7 +245,7 @@ export class FleetPlugin
ui: ['show', 'read', 'write'],
},
read: {
api: [`fleet-read`, `integrations-read`],
api: [`${PLUGIN_ID}-read`, `integrations-read`],
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
catalogue: ['fleet'], // TODO: check if this is actually available to read user
savedObject: {
@ -260,19 +258,33 @@ export class FleetPlugin
});
}
core.http.registerRouteHandlerContext<FleetRequestHandlerContext, 'fleet'>(
'fleet',
async (coreContext, request) => ({
authz: await getAuthzFromRequest(request),
epm: {
// Use a lazy getter to avoid constructing this client when not used by a request handler
get internalSoClient() {
return appContextService
.getSavedObjects()
.getScopedClient(request, { excludedWrappers: ['security'] });
core.http.registerRouteHandlerContext<FleetRequestHandlerContext, typeof PLUGIN_ID>(
PLUGIN_ID,
async (context, request) => {
const plugin = this;
return {
get agentClient() {
const agentService = plugin.setupAgentService(
context.core.elasticsearch.client.asInternalUser
);
return {
asCurrentUser: agentService.asScoped(request),
asInternalUser: agentService.asInternalUser,
};
},
},
})
authz: await getAuthzFromRequest(request),
epm: {
// Use a lazy getter to avoid constructing this client when not used by a request handler
get internalSoClient() {
return appContextService
.getSavedObjects()
.getScopedClient(request, { excludedWrappers: ['security'] });
},
},
};
}
);
const router: FleetRouter = core.http.createRouter<FleetRequestHandlerContext>();
@ -365,12 +377,7 @@ export class FleetPlugin
getInstallation,
ensureInstalledPackage,
},
agentService: {
getAgent: getAgentById,
listAgents: getAgentsByKuery,
getAgentStatusById,
getAgentStatusForAgentPolicy,
},
agentService: this.setupAgentService(core.elasticsearch.client.asInternalUser),
agentPolicyService: {
get: agentPolicyService.get,
list: agentPolicyService.list,
@ -394,4 +401,13 @@ export class FleetPlugin
licenseService.stop();
this.telemetryEventsSender.stop();
}
private setupAgentService(internalEsClient: ElasticsearchClient): AgentService {
if (this.agentService) {
return this.agentService;
}
this.agentService = new AgentServiceImpl(internalEsClient);
return this.agentService;
}
}

View file

@ -24,7 +24,7 @@ function checkSecurityEnabled() {
return appContextService.hasSecurity() && appContextService.getSecurityLicense().isEnabled();
}
function checkSuperuser(req: KibanaRequest) {
export function checkSuperuser(req: KibanaRequest) {
if (!checkSecurityEnabled()) {
return false;
}

View file

@ -9,7 +9,8 @@ import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks';
import type { PostFleetSetupResponse } from '../../../common';
import { RegistryError } from '../../errors';
import { createAppContextStartContractMock, xpackMocks, fleetAuthzMock } from '../../mocks';
import { createAppContextStartContractMock, xpackMocks, createFleetAuthzMock } from '../../mocks';
import { agentServiceMock } from '../../services/agents/agent_service.mock';
import { appContextService } from '../../services/app_context';
import { setupFleet } from '../../services/setup';
import type { FleetRequestHandlerContext } from '../../types';
@ -34,7 +35,11 @@ describe('FleetSetupHandler', () => {
context = {
...xpackMocks.createRequestHandlerContext(),
fleet: {
authz: fleetAuthzMock,
agentClient: {
asCurrentUser: agentServiceMock.createClient(),
asInternalUser: agentServiceMock.createClient(),
},
authz: createFleetAuthzMock(),
epm: {
internalSoClient: savedObjectsClientMock.create(),
},

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { AgentClient, AgentService } from './agent_service';
const createClientMock = (): jest.Mocked<AgentClient> => ({
getAgent: jest.fn(),
getAgentStatusById: jest.fn(),
getAgentStatusForAgentPolicy: jest.fn(),
listAgents: jest.fn(),
});
const createServiceMock = (): jest.Mocked<AgentService> => ({
asInternalUser: createClientMock(),
asScoped: jest.fn().mockReturnValue(createClientMock()),
});
export const agentServiceMock = {
createClient: createClientMock,
create: createServiceMock,
};

View file

@ -0,0 +1,132 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
jest.mock('../../routes/security');
jest.mock('./crud');
jest.mock('./status');
import type { ElasticsearchClient } from '../../../../../../src/core/server';
import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { FleetUnauthorizedError } from '../../errors';
import { checkSuperuser } from '../../routes/security';
import type { AgentClient } from './agent_service';
import { AgentServiceImpl } from './agent_service';
import { getAgentsByKuery, getAgentById } from './crud';
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
const mockCheckSuperuser = checkSuperuser as jest.Mock<boolean>;
const mockGetAgentsByKuery = getAgentsByKuery as jest.Mock;
const mockGetAgentById = getAgentById as jest.Mock;
const mockGetAgentStatusById = getAgentStatusById as jest.Mock;
const mockGetAgentStatusForAgentPolicy = getAgentStatusForAgentPolicy as jest.Mock;
describe('AgentService', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('asScoped', () => {
describe('without required privilege', () => {
const agentClient = new AgentServiceImpl(
elasticsearchServiceMock.createElasticsearchClient()
).asScoped(httpServerMock.createKibanaRequest());
beforeEach(() => mockCheckSuperuser.mockReturnValue(false));
it('rejects on listAgents', async () => {
await expect(agentClient.listAgents({ showInactive: true })).rejects.toThrowError(
new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
)
);
});
it('rejects on getAgent', async () => {
await expect(agentClient.getAgent('foo')).rejects.toThrowError(
new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
)
);
});
it('rejects on getAgentStatusById', async () => {
await expect(agentClient.getAgentStatusById('foo')).rejects.toThrowError(
new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
)
);
});
it('rejects on getAgentStatusForAgentPolicy', async () => {
await expect(agentClient.getAgentStatusForAgentPolicy()).rejects.toThrowError(
new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
)
);
});
});
describe('with required privilege', () => {
const mockEsClient = elasticsearchServiceMock.createElasticsearchClient();
const agentClient = new AgentServiceImpl(mockEsClient).asScoped(
httpServerMock.createKibanaRequest()
);
beforeEach(() => mockCheckSuperuser.mockReturnValue(true));
expectApisToCallServicesSuccessfully(mockEsClient, agentClient);
});
});
describe('asInternalUser', () => {
const mockEsClient = elasticsearchServiceMock.createElasticsearchClient();
const agentClient = new AgentServiceImpl(mockEsClient).asInternalUser;
expectApisToCallServicesSuccessfully(mockEsClient, agentClient);
});
});
function expectApisToCallServicesSuccessfully(
mockEsClient: ElasticsearchClient,
agentClient: AgentClient
) {
test('client.listAgents calls getAgentsByKuery and returns results', async () => {
mockGetAgentsByKuery.mockResolvedValue('getAgentsByKuery success');
await expect(agentClient.listAgents({ showInactive: true })).resolves.toEqual(
'getAgentsByKuery success'
);
expect(mockGetAgentsByKuery).toHaveBeenCalledWith(mockEsClient, { showInactive: true });
});
test('client.getAgent calls getAgentById and returns results', async () => {
mockGetAgentById.mockResolvedValue('getAgentById success');
await expect(agentClient.getAgent('foo-id')).resolves.toEqual('getAgentById success');
expect(mockGetAgentById).toHaveBeenCalledWith(mockEsClient, 'foo-id');
});
test('client.getAgentStatusById calls getAgentStatusById and returns results', async () => {
mockGetAgentStatusById.mockResolvedValue('getAgentStatusById success');
await expect(agentClient.getAgentStatusById('foo-id')).resolves.toEqual(
'getAgentStatusById success'
);
expect(mockGetAgentStatusById).toHaveBeenCalledWith(mockEsClient, 'foo-id');
});
test('client.getAgentStatusForAgentPolicy calls getAgentStatusForAgentPolicy and returns results', async () => {
mockGetAgentStatusForAgentPolicy.mockResolvedValue('getAgentStatusForAgentPolicy success');
await expect(agentClient.getAgentStatusForAgentPolicy('foo-id', 'foo-filter')).resolves.toEqual(
'getAgentStatusForAgentPolicy success'
);
expect(mockGetAgentStatusForAgentPolicy).toHaveBeenCalledWith(
mockEsClient,
'foo-id',
'foo-filter'
);
});
}

View file

@ -0,0 +1,140 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
/* eslint-disable max-classes-per-file */
import type { ElasticsearchClient, KibanaRequest } from 'kibana/server';
import type { AgentStatus, ListWithKuery } from '../../types';
import type { Agent, GetAgentStatusResponse } from '../../../common';
import { checkSuperuser } from '../../routes/security';
import { FleetUnauthorizedError } from '../../errors';
import { getAgentsByKuery, getAgentById } from './crud';
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
/**
* A service for interacting with Agent data. See {@link AgentClient} for more information.
*
* @public
*/
export interface AgentService {
/**
* Should be used for end-user requests to Kibana. APIs will return errors if user does not have appropriate access.
*/
asScoped(req: KibanaRequest): AgentClient;
/**
* Only use for server-side usages (eg. telemetry), should not be used for end users unless an explicit authz check is
* done.
*/
asInternalUser: AgentClient;
}
/**
* A client for interacting with data about an Agent
*
* @public
*/
export interface AgentClient {
/**
* Get an Agent by id
*/
getAgent(agentId: string): Promise<Agent>;
/**
* Return the status by the Agent's id
*/
getAgentStatusById(agentId: string): Promise<AgentStatus>;
/**
* Return the status by the Agent's Policy id
*/
getAgentStatusForAgentPolicy(
agentPolicyId?: string,
filterKuery?: string
): Promise<GetAgentStatusResponse['results']>;
/**
* List agents
*/
listAgents(
options: ListWithKuery & {
showInactive: boolean;
}
): Promise<{
agents: Agent[];
total: number;
page: number;
perPage: number;
}>;
}
/**
* @internal
*/
class AgentClientImpl implements AgentClient {
constructor(
private readonly internalEsClient: ElasticsearchClient,
private readonly preflightCheck?: () => void | Promise<void>
) {}
public async listAgents(
options: ListWithKuery & {
showInactive: boolean;
}
) {
await this.#runPreflight();
return getAgentsByKuery(this.internalEsClient, options);
}
public async getAgent(agentId: string) {
await this.#runPreflight();
return getAgentById(this.internalEsClient, agentId);
}
public async getAgentStatusById(agentId: string) {
await this.#runPreflight();
return getAgentStatusById(this.internalEsClient, agentId);
}
public async getAgentStatusForAgentPolicy(agentPolicyId?: string, filterKuery?: string) {
await this.#runPreflight();
return getAgentStatusForAgentPolicy(this.internalEsClient, agentPolicyId, filterKuery);
}
#runPreflight = async () => {
if (this.preflightCheck) {
return this.preflightCheck();
}
};
}
/**
* @internal
*/
export class AgentServiceImpl implements AgentService {
constructor(private readonly internalEsClient: ElasticsearchClient) {}
public asScoped(req: KibanaRequest) {
const preflightCheck = () => {
if (!checkSuperuser(req)) {
throw new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
);
}
};
return new AgentClientImpl(this.internalEsClient, preflightCheck);
}
public get asInternalUser() {
return new AgentClientImpl(this.internalEsClient);
}
}

View file

@ -13,3 +13,5 @@ export * from './update';
export * from './actions';
export * from './reassign';
export * from './setup';
export { AgentServiceImpl } from './agent_service';
export type { AgentClient, AgentService } from './agent_service';

View file

@ -5,13 +5,8 @@
* 2.0.
*/
import type { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
import type { SavedObjectsClientContract } from 'kibana/server';
import type { AgentStatus } from '../types';
import type { GetAgentStatusResponse } from '../../common';
import type { getAgentById, getAgentsByKuery } from './agents';
import type { agentPolicyService } from './agent_policy';
import * as settingsService from './settings';
import type { getInstallation, ensureInstalledPackage } from './epm/packages';
@ -39,32 +34,6 @@ export interface PackageService {
ensureInstalledPackage: typeof ensureInstalledPackage;
}
/**
* A service that provides exported functions that return information about an Agent
*/
export interface AgentService {
/**
* Get an Agent by id
*/
getAgent: typeof getAgentById;
/**
* Return the status by the Agent's id
*/
getAgentStatusById(esClient: ElasticsearchClient, agentId: string): Promise<AgentStatus>;
/**
* Return the status by the Agent's Policy id
*/
getAgentStatusForAgentPolicy(
esClient: ElasticsearchClient,
agentPolicyId?: string,
filterKuery?: string
): Promise<GetAgentStatusResponse['results']>;
/**
* List agents
*/
listAgents: typeof getAgentsByKuery;
}
export interface AgentPolicyServiceInterface {
get: typeof agentPolicyService['get'];
list: typeof agentPolicyService['list'];
@ -73,6 +42,10 @@ export interface AgentPolicyServiceInterface {
getByIds: typeof agentPolicyService['getByIDs'];
}
// Agent services
export { AgentServiceImpl } from './agents';
export type { AgentClient, AgentService } from './agents';
// Saved object services
export { agentPolicyService } from './agent_policy';
export { packagePolicyService } from './package_policy';

View file

@ -14,11 +14,18 @@ import type {
IRouter,
} from '../../../../../src/core/server';
import type { FleetAuthz } from '../../common/authz';
import type { AgentClient } from '../services';
/** @internal */
export interface FleetRequestHandlerContext extends RequestHandlerContext {
fleet: {
/** {@link FleetAuthz} */
authz: FleetAuthz;
/** {@link AgentClient} */
agentClient: {
asCurrentUser: AgentClient;
asInternalUser: AgentClient;
};
epm: {
/**
* Saved Objects client configured to use kibana_system privileges instead of end-user privileges. Should only be

View file

@ -6,7 +6,7 @@
*/
import { uniq } from 'lodash';
import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
import type { KibanaRequest, SavedObjectsClientContract } from 'src/core/server';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../fleet/common';
import { OSQUERY_INTEGRATION_NAME } from '../../common';
import { OsqueryAppContext } from './osquery_app_context_services';
@ -34,7 +34,7 @@ const aggregateResults = async (
};
export const parseAgentSelection = async (
esClient: ElasticsearchClient,
request: KibanaRequest,
soClient: SavedObjectsClientContract,
context: OsqueryAppContext,
agentSelection: AgentSelection
@ -42,7 +42,7 @@ export const parseAgentSelection = async (
const selectedAgents: Set<string> = new Set();
const addAgent = selectedAgents.add.bind(selectedAgents);
const { allAgentsSelected, platformsSelected, policiesSelected, agents } = agentSelection;
const agentService = context.service.getAgentService();
const agentService = context.service.getAgentService()?.asScoped(request);
const packagePolicyService = context.service.getPackagePolicyService();
const kueryFragments = [];
@ -59,7 +59,7 @@ export const parseAgentSelection = async (
if (allAgentsSelected) {
const kuery = kueryFragments.join(' and ');
const fetchedAgents = await aggregateResults(async (page, perPage) => {
const res = await agentService.listAgents(esClient, {
const res = await agentService.listAgents({
perPage,
page,
kuery,
@ -80,7 +80,7 @@ export const parseAgentSelection = async (
kueryFragments.push(`(${groupFragments.join(' or ')})`);
const kuery = kueryFragments.join(' and ');
const fetchedAgents = await aggregateResults(async (page, perPage) => {
const res = await agentService.listAgents(esClient, {
const res = await agentService.listAgents({
perPage,
page,
kuery,

View file

@ -46,7 +46,7 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon
const { agentSelection } = request.body as { agentSelection: AgentSelection };
const selectedAgents = await parseAgentSelection(
esClient,
request,
soClient,
osqueryContext,
agentSelection

View file

@ -30,7 +30,6 @@ export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAp
},
async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asInternalUser;
const agentService = osqueryContext.service.getAgentService();
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
@ -51,7 +50,8 @@ export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAp
agentPolicies,
(agentPolicy: GetAgentPoliciesResponseItem) =>
agentService
?.getAgentStatusForAgentPolicy(esClient, agentPolicy.id)
?.asScoped(request)
.getAgentStatusForAgentPolicy(agentPolicy.id)
.then(({ total: agentTotal }) => (agentPolicy.agents = agentTotal)),
{ concurrency: 10 }
);

View file

@ -28,11 +28,10 @@ export const getAgentStatusForAgentPolicyRoute = (
options: { tags: [`access:${PLUGIN_ID}-read`] },
},
async (context, request, response) => {
const esClient = context.core.elasticsearch.client.asInternalUser;
const results = await osqueryContext.service
.getAgentService()
?.getAgentStatusForAgentPolicy(esClient, request.query.policyId, request.query.kuery);
?.asScoped(request)
.getAgentStatusForAgentPolicy(request.query.policyId, request.query.kuery);
if (!results) {
return response.ok({ body: {} });

View file

@ -20,14 +20,13 @@ export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContex
options: { tags: [`access:${PLUGIN_ID}-read`] },
},
async (context, request, response) => {
const esClient = context.core.elasticsearch.client.asInternalUser;
let agents;
try {
agents = await osqueryContext.service
.getAgentService()
?.asScoped(request)
// @ts-expect-error update types
?.listAgents(esClient, request.query);
.listAgents(request.query);
} catch (error) {
return response.badRequest({ body: error });
}

View file

@ -34,6 +34,10 @@ import {
EndpointAppContentServicesNotSetUpError,
EndpointAppContentServicesNotStartedError,
} from './errors';
import {
EndpointFleetServicesFactory,
EndpointScopedFleetServicesInterface,
} from './services/endpoint_fleet_services';
export interface EndpointAppContextServiceSetupContract {
securitySolutionRequestContextFactory: IRequestContextFactory;
@ -64,6 +68,7 @@ export type EndpointAppContextServiceStartContract = Partial<
export class EndpointAppContextService {
private setupDependencies: EndpointAppContextServiceSetupContract | null = null;
private startDependencies: EndpointAppContextServiceStartContract | null = null;
private fleetServicesFactory: EndpointFleetServicesFactory | null = null;
public security: SecurityPluginStart | undefined;
public setup(dependencies: EndpointAppContextServiceSetupContract) {
@ -78,6 +83,17 @@ export class EndpointAppContextService {
this.startDependencies = dependencies;
this.security = dependencies.security;
// let's try to avoid turning off eslint's Forbidden non-null assertion rule
const { agentService, agentPolicyService, packagePolicyService, packageService } =
dependencies as Required<EndpointAppContextServiceStartContract>;
this.fleetServicesFactory = new EndpointFleetServicesFactory({
agentService,
agentPolicyService,
packagePolicyService,
packageService,
});
if (dependencies.registerIngestCallback && dependencies.manifestManager) {
dependencies.registerIngestCallback(
'packagePolicyCreate',
@ -119,10 +135,20 @@ export class EndpointAppContextService {
return this.startDependencies.endpointMetadataService;
}
public getScopedFleetServices(req: KibanaRequest): EndpointScopedFleetServicesInterface {
if (this.fleetServicesFactory === null) {
throw new EndpointAppContentServicesNotStartedError();
}
return this.fleetServicesFactory.asScoped(req);
}
/** @deprecated use `getScopedFleetServices()` instead */
public getAgentService(): AgentService | undefined {
return this.startDependencies?.agentService;
}
/** @deprecated use `getScopedFleetServices()` instead */
public getPackagePolicyService(): PackagePolicyServiceInterface {
if (!this.startDependencies?.packagePolicyService) {
throw new EndpointAppContentServicesNotStartedError();
@ -130,6 +156,7 @@ export class EndpointAppContextService {
return this.startDependencies?.packagePolicyService;
}
/** @deprecated use `getScopedFleetServices()` instead */
public getAgentPolicyService(): AgentPolicyServiceInterface | undefined {
return this.startDependencies?.agentPolicyService;
}

View file

@ -17,7 +17,7 @@ import {
createMockAgentPolicyService,
createMockAgentService,
createArtifactsClientMock,
fleetAuthzMock,
createFleetAuthzMock,
} from '../../../fleet/server/mocks';
import { createMockConfig } from '../lib/detection_engine/routes/__mocks__';
import {
@ -96,7 +96,6 @@ export const createMockEndpointAppContextServiceStartContract =
const packagePolicyService = createPackagePolicyServiceMock();
const endpointMetadataService = new EndpointMetadataService(
savedObjectsStart,
agentService,
agentPolicyService,
packagePolicyService,
logger
@ -155,7 +154,7 @@ export const createMockPackageService = (): jest.Mocked<PackageService> => {
export const createMockFleetStartContract = (indexPattern: string): FleetStartContract => {
return {
authz: {
fromRequest: jest.fn().mockResolvedValue(fleetAuthzMock),
fromRequest: jest.fn().mockResolvedValue(createFleetAuthzMock()),
},
fleetSetupCompleted: jest.fn().mockResolvedValue(undefined),
esIndexPatternService: {

View file

@ -9,6 +9,7 @@ import { HostStatus } from '../../../../common/endpoint/types';
import { createMockMetadataRequestContext } from '../../mocks';
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
import { enrichHostMetadata, MetadataRequestContext } from './handlers';
import { AgentClient } from '../../../../../fleet/server';
describe('test document enrichment', () => {
let metaReqCtx: jest.Mocked<MetadataRequestContext>;
@ -23,11 +24,9 @@ describe('test document enrichment', () => {
beforeEach(() => {
statusFn = jest.fn();
(metaReqCtx.endpointAppContextService.getAgentService as jest.Mock).mockImplementation(() => {
return {
getAgentStatusById: statusFn,
};
});
metaReqCtx.requestHandlerContext!.fleet!.agentClient.asCurrentUser = {
getAgentStatusById: statusFn,
} as unknown as AgentClient;
});
it('should return host healthy for online agent', async () => {
@ -87,12 +86,10 @@ describe('test document enrichment', () => {
beforeEach(() => {
agentMock = jest.fn();
agentPolicyMock = jest.fn();
(metaReqCtx.endpointAppContextService.getAgentService as jest.Mock).mockImplementation(() => {
return {
getAgent: agentMock,
getAgentStatusById: jest.fn(),
};
});
metaReqCtx.requestHandlerContext!.fleet!.agentClient.asCurrentUser = {
getAgent: agentMock,
getAgentStatusById: jest.fn(),
} as unknown as AgentClient;
(metaReqCtx.endpointAppContextService.getAgentPolicyService as jest.Mock).mockImplementation(
() => {
return {

View file

@ -35,7 +35,6 @@ import { EndpointAppContextService } from '../../endpoint_app_context_services';
import { fleetAgentStatusToEndpointHostStatus } from '../../utils';
import { queryResponseToHostListResult } from './support/query_strategies';
import { NotFoundError } from '../../errors';
import { EndpointError } from '../../../../common/endpoint/errors';
import { EndpointHostUnEnrolledError } from '../../services/metadata';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
import { GetMetadataListRequestQuery } from '../../../../common/endpoint/schema/metadata';
@ -43,6 +42,7 @@ import {
ENDPOINT_DEFAULT_PAGE,
ENDPOINT_DEFAULT_PAGE_SIZE,
} from '../../../../common/endpoint/constants';
import { EndpointFleetServicesInterface } from '../../services/endpoint_fleet_services';
export interface MetadataRequestContext {
esClient?: IScopedClusterClient;
@ -93,9 +93,7 @@ export const getMetadataListRequestHandler = function (
> {
return async (context, request, response) => {
const endpointMetadataService = endpointAppContext.service.getEndpointMetadataService();
if (!endpointMetadataService) {
throw new EndpointError('endpoint metadata service not available');
}
const fleetServices = endpointAppContext.service.getScopedFleetServices(request);
let doesUnitedIndexExist = false;
let didUnitedIndexError = false;
@ -118,18 +116,25 @@ export const getMetadataListRequestHandler = function (
// If no unified Index present, then perform a search using the legacy approach
if (!doesUnitedIndexExist || didUnitedIndexError) {
const endpointPolicies = await getAllEndpointPackagePolicies(
endpointAppContext.service.getPackagePolicyService(),
fleetServices.packagePolicy,
context.core.savedObjects.client
);
const pagingProperties = await getPagingProperties(request, endpointAppContext);
body = await legacyListMetadataQuery(context, endpointAppContext, logger, endpointPolicies, {
page: pagingProperties.pageIndex,
pageSize: pagingProperties.pageSize,
kuery: request?.body?.filters?.kql || '',
hostStatuses: request?.body?.filters?.host_status || [],
});
body = await legacyListMetadataQuery(
context,
endpointAppContext,
fleetServices,
logger,
endpointPolicies,
{
page: pagingProperties.pageIndex,
pageSize: pagingProperties.pageSize,
kuery: request?.body?.filters?.kql || '',
hostStatuses: request?.body?.filters?.host_status || [],
}
);
return response.ok({ body });
}
@ -138,6 +143,7 @@ export const getMetadataListRequestHandler = function (
const pagingProperties = await getPagingProperties(request, endpointAppContext);
const { data, total } = await endpointMetadataService.getHostMetadataList(
context.core.elasticsearch.client.asCurrentUser,
fleetServices,
{
page: pagingProperties.pageIndex,
pageSize: pagingProperties.pageSize,
@ -171,6 +177,7 @@ export function getMetadataListRequestHandlerV2(
> {
return async (context, request, response) => {
const endpointMetadataService = endpointAppContext.service.getEndpointMetadataService();
const fleetServices = endpointAppContext.service.getScopedFleetServices(request);
let doesUnitedIndexExist = false;
let didUnitedIndexError = false;
@ -193,13 +200,14 @@ export function getMetadataListRequestHandlerV2(
// If no unified Index present, then perform a search using the legacy approach
if (!doesUnitedIndexExist || didUnitedIndexError) {
const endpointPolicies = await getAllEndpointPackagePolicies(
endpointAppContext.service.getPackagePolicyService(),
fleetServices.packagePolicy,
context.core.savedObjects.client
);
const legacyResponse = await legacyListMetadataQuery(
context,
endpointAppContext,
fleetServices,
logger,
endpointPolicies,
request.query
@ -217,6 +225,7 @@ export function getMetadataListRequestHandlerV2(
try {
const { data, total } = await endpointMetadataService.getHostMetadataList(
context.core.elasticsearch.client.asCurrentUser,
fleetServices,
request.query
);
@ -250,6 +259,7 @@ export const getMetadataRequestHandler = function (
return response.ok({
body: await endpointMetadataService.getEnrichedHostMetadata(
context.core.elasticsearch.client.asCurrentUser,
endpointAppContext.service.getScopedFleetServices(request),
request.params.id
),
});
@ -314,10 +324,6 @@ export async function enrichHostMetadata(
throw e;
}
const esClient = (metadataRequestContext?.esClient ??
metadataRequestContext.requestHandlerContext?.core.elasticsearch
.client) as IScopedClusterClient;
const esSavedObjectClient =
metadataRequestContext?.savedObjectsClient ??
(metadataRequestContext.requestHandlerContext?.core.savedObjects
@ -333,9 +339,10 @@ export async function enrichHostMetadata(
log.warn(`Missing elastic agent id, using host id instead ${elasticAgentId}`);
}
const status = await metadataRequestContext.endpointAppContextService
?.getAgentService()
?.getAgentStatusById(esClient.asCurrentUser, elasticAgentId);
const status =
await metadataRequestContext.requestHandlerContext?.fleet?.agentClient.asCurrentUser.getAgentStatusById(
elasticAgentId
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
hostStatus = fleetAgentStatusToEndpointHostStatus(status!);
} catch (e) {
@ -349,9 +356,10 @@ export async function enrichHostMetadata(
let policyInfo: HostInfo['policy_info'];
try {
const agent = await metadataRequestContext.endpointAppContextService
?.getAgentService()
?.getAgent(esClient.asCurrentUser, elasticAgentId);
const agent =
await metadataRequestContext.requestHandlerContext?.fleet?.agentClient.asCurrentUser.getAgent(
elasticAgentId
);
const agentPolicy = await metadataRequestContext.endpointAppContextService
.getAgentPolicyService()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@ -393,15 +401,12 @@ export async function enrichHostMetadata(
async function legacyListMetadataQuery(
context: SecuritySolutionRequestHandlerContext,
endpointAppContext: EndpointAppContext,
fleetServices: EndpointFleetServicesInterface,
logger: Logger,
endpointPolicies: PackagePolicy[],
queryOptions: GetMetadataListRequestQuery
): Promise<HostResultList> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const agentService = endpointAppContext.service.getAgentService()!;
if (agentService === undefined) {
throw new Error('agentService not available');
}
const fleetAgentClient = fleetServices.agent;
const metadataRequestContext: MetadataRequestContext = {
esClient: context.core.elasticsearch.client,
@ -412,14 +417,15 @@ async function legacyListMetadataQuery(
};
const endpointPolicyIds = endpointPolicies.map((policy) => policy.policy_id);
const unenrolledAgentIds = await findAllUnenrolledAgentIds(
agentService,
fleetAgentClient,
context.core.elasticsearch.client.asCurrentUser,
endpointPolicyIds
);
const statusAgentIds = await findAgentIdsByStatus(
agentService,
fleetAgentClient,
context.core.elasticsearch.client.asCurrentUser,
queryOptions?.hostStatuses || []
);

View file

@ -43,7 +43,7 @@ import {
legacyMetadataSearchResponseMock,
unitedMetadataSearchResponseMock,
} from './support/test_support';
import { PackageService } from '../../../../../fleet/server/services';
import { AgentClient, PackageService } from '../../../../../fleet/server/services';
import {
HOST_METADATA_GET_ROUTE,
HOST_METADATA_LIST_ROUTE,
@ -60,6 +60,7 @@ import {
} from '../../../../../../../src/core/server/elasticsearch/client/mocks';
import { EndpointHostNotFoundError } from '../../services/metadata';
import { FleetAgentGenerator } from '../../../../common/endpoint/data_generators/fleet_agent_generator';
import { createMockAgentClient } from '../../../../../fleet/server/mocks';
class IndexNotFoundException extends Error {
meta: { body: { error: { type: string } } };
@ -88,6 +89,7 @@ describe('test endpoint routes', () => {
let mockAgentPolicyService: Required<
ReturnType<typeof createMockEndpointAppContextServiceStartContract>
>['agentPolicyService'];
let mockAgentClient: jest.Mocked<AgentClient>;
let endpointAppContextService: EndpointAppContextService;
let startContract: EndpointAppContextServiceStartContract;
const noUnenrolledAgent = {
@ -151,6 +153,8 @@ describe('test endpoint routes', () => {
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
endpointAppContextService.start({ ...startContract, packageService: mockPackageService });
mockAgentService = startContract.agentService!;
mockAgentClient = createMockAgentClient();
mockAgentService.asScoped = () => mockAgentClient;
mockAgentPolicyService = startContract.agentPolicyService!;
registerEndpointRoutes(routerMock, {
@ -176,8 +180,8 @@ describe('test endpoint routes', () => {
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
path.startsWith(HOST_METADATA_LIST_ROUTE)
)!;
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
await routeHandler(
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
@ -220,8 +224,8 @@ describe('test endpoint routes', () => {
},
});
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
mockAgentPolicyService.getByIds = jest.fn().mockResolvedValueOnce([]);
const metadata = new EndpointDocGenerator().generateHostMetadata();
const esSearchMock = mockScopedClient.asCurrentUser.search as jest.Mock;
@ -415,6 +419,8 @@ describe('test endpoint routes', () => {
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
endpointAppContextService.start({ ...startContract, packageService: mockPackageService });
mockAgentService = startContract.agentService!;
mockAgentClient = createMockAgentClient();
mockAgentService.asScoped = () => mockAgentClient;
registerEndpointRoutes(routerMock, {
logFactory: loggingSystemMock.create(),
@ -439,8 +445,8 @@ describe('test endpoint routes', () => {
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
path.startsWith(HOST_METADATA_LIST_ROUTE)
)!;
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
await routeHandler(
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
@ -474,8 +480,8 @@ describe('test endpoint routes', () => {
},
});
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
(mockScopedClient.asCurrentUser.search as jest.Mock)
.mockImplementationOnce(() => {
throw new IndexNotFoundException();
@ -536,8 +542,8 @@ describe('test endpoint routes', () => {
},
});
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
(mockScopedClient.asCurrentUser.search as jest.Mock)
.mockImplementationOnce(() => {
throw new IndexNotFoundException();
@ -653,6 +659,8 @@ describe('test endpoint routes', () => {
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
endpointAppContextService.start({ ...startContract, packageService: mockPackageService });
mockAgentService = startContract.agentService!;
mockAgentClient = createMockAgentClient();
mockAgentService.asScoped = () => mockAgentClient;
mockAgentPolicyService = startContract.agentPolicyService!;
registerEndpointRoutes(routerMock, {
@ -683,8 +691,8 @@ describe('test endpoint routes', () => {
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
path.startsWith(HOST_METADATA_LIST_ROUTE)
)!;
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
await routeHandler(
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
@ -718,8 +726,8 @@ describe('test endpoint routes', () => {
},
});
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
mockAgentPolicyService.getByIds = jest.fn().mockResolvedValueOnce([]);
const metadata = new EndpointDocGenerator().generateHostMetadata();
const esSearchMock = mockScopedClient.asCurrentUser.search as jest.Mock;
@ -913,6 +921,8 @@ describe('test endpoint routes', () => {
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
endpointAppContextService.start({ ...startContract, packageService: mockPackageService });
mockAgentService = startContract.agentService!;
mockAgentClient = createMockAgentClient();
mockAgentService.asScoped = () => mockAgentClient;
registerEndpointRoutes(routerMock, {
logFactory: loggingSystemMock.create(),
@ -942,8 +952,8 @@ describe('test endpoint routes', () => {
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
path.startsWith(HOST_METADATA_LIST_ROUTE)
)!;
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
await routeHandler(
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
@ -971,8 +981,8 @@ describe('test endpoint routes', () => {
},
});
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
(mockScopedClient.asCurrentUser.search as jest.Mock)
.mockImplementationOnce(() => {
throw new IndexNotFoundException();
@ -1026,8 +1036,8 @@ describe('test endpoint routes', () => {
},
});
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent);
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.listAgents.mockResolvedValue(noUnenrolledAgent);
(mockScopedClient.asCurrentUser.search as jest.Mock)
.mockImplementationOnce(() => {
throw new IndexNotFoundException();
@ -1142,6 +1152,8 @@ describe('test endpoint routes', () => {
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
endpointAppContextService.start({ ...startContract, packageService: mockPackageService });
mockAgentService = startContract.agentService!;
mockAgentClient = createMockAgentClient();
mockAgentService.asScoped = () => mockAgentClient;
registerEndpointRoutes(routerMock, {
logFactory: loggingSystemMock.create(),
@ -1160,8 +1172,8 @@ describe('test endpoint routes', () => {
Promise.resolve({ body: legacyMetadataSearchResponseMock() })
);
mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error');
mockAgentService.getAgent = jest.fn().mockReturnValue({
mockAgentClient.getAgentStatusById.mockResolvedValue('error');
mockAgentClient.getAgent.mockResolvedValue({
active: true,
} as unknown as Agent);
@ -1192,9 +1204,7 @@ describe('test endpoint routes', () => {
params: { id: response.hits.hits[0]._id },
});
mockAgentService.getAgent = jest
.fn()
.mockReturnValue(agentGenerator.generate({ status: 'online' }));
mockAgentClient.getAgent.mockResolvedValue(agentGenerator.generate({ status: 'online' }));
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({ body: response })
);
@ -1229,7 +1239,7 @@ describe('test endpoint routes', () => {
params: { id: response.hits.hits[0]._id },
});
mockAgentService.getAgent = jest.fn().mockRejectedValue(new AgentNotFoundError('not found'));
mockAgentClient.getAgent.mockRejectedValue(new AgentNotFoundError('not found'));
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({ body: response })
@ -1264,7 +1274,7 @@ describe('test endpoint routes', () => {
params: { id: response.hits.hits[0]._id },
});
mockAgentService.getAgent = jest.fn().mockReturnValue(
mockAgentClient.getAgent.mockResolvedValue(
agentGenerator.generate({
status: 'error',
})
@ -1304,7 +1314,7 @@ describe('test endpoint routes', () => {
(mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({ body: response })
);
mockAgentService.getAgent = jest.fn().mockReturnValue({
mockAgentClient.getAgent.mockResolvedValue({
active: false,
} as unknown as Agent);

View file

@ -8,21 +8,21 @@
import { ElasticsearchClient } from 'kibana/server';
import { buildStatusesKuery, findAgentIdsByStatus } from './agent_status';
import { elasticsearchServiceMock } from '../../../../../../../../src/core/server/mocks';
import { AgentService } from '../../../../../../fleet/server/services';
import { createMockAgentService } from '../../../../../../fleet/server/mocks';
import { AgentClient } from '../../../../../../fleet/server/services';
import { createMockAgentClient } from '../../../../../../fleet/server/mocks';
import { Agent } from '../../../../../../fleet/common/types/models';
import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services';
describe('test filtering endpoint hosts by agent status', () => {
let mockElasticsearchClient: jest.Mocked<ElasticsearchClient>;
let mockAgentService: jest.Mocked<AgentService>;
let mockAgentClient: jest.Mocked<AgentClient>;
beforeEach(() => {
mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
mockAgentService = createMockAgentService();
mockAgentClient = createMockAgentClient();
});
it('will accept a valid status condition', async () => {
mockAgentService.listAgents.mockImplementationOnce(() =>
mockAgentClient.listAgents.mockImplementationOnce(() =>
Promise.resolve({
agents: [],
total: 0,
@ -31,14 +31,14 @@ describe('test filtering endpoint hosts by agent status', () => {
})
);
const result = await findAgentIdsByStatus(mockAgentService, mockElasticsearchClient, [
const result = await findAgentIdsByStatus(mockAgentClient, mockElasticsearchClient, [
'healthy',
]);
expect(result).toBeDefined();
});
it('will filter for offline hosts', async () => {
mockAgentService.listAgents
mockAgentClient.listAgents
.mockImplementationOnce(() =>
Promise.resolve({
agents: [{ id: 'id1' } as unknown as Agent, { id: 'id2' } as unknown as Agent],
@ -56,11 +56,11 @@ describe('test filtering endpoint hosts by agent status', () => {
})
);
const result = await findAgentIdsByStatus(mockAgentService, mockElasticsearchClient, [
const result = await findAgentIdsByStatus(mockAgentClient, mockElasticsearchClient, [
'offline',
]);
const offlineKuery = AgentStatusKueryHelper.buildKueryForOfflineAgents();
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
expect(mockAgentClient.listAgents.mock.calls[0][0].kuery).toEqual(
expect.stringContaining(offlineKuery)
);
expect(result).toBeDefined();
@ -68,7 +68,7 @@ describe('test filtering endpoint hosts by agent status', () => {
});
it('will filter for multiple statuses', async () => {
mockAgentService.listAgents
mockAgentClient.listAgents
.mockImplementationOnce(() =>
Promise.resolve({
agents: [{ id: 'A' } as unknown as Agent, { id: 'B' } as unknown as Agent],
@ -86,13 +86,13 @@ describe('test filtering endpoint hosts by agent status', () => {
})
);
const result = await findAgentIdsByStatus(mockAgentService, mockElasticsearchClient, [
const result = await findAgentIdsByStatus(mockAgentClient, mockElasticsearchClient, [
'updating',
'unhealthy',
]);
const unenrollKuery = AgentStatusKueryHelper.buildKueryForUpdatingAgents();
const errorKuery = AgentStatusKueryHelper.buildKueryForErrorAgents();
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
expect(mockAgentClient.listAgents.mock.calls[0][0].kuery).toEqual(
expect.stringContaining(`${unenrollKuery} OR ${errorKuery}`)
);
expect(result).toBeDefined();

View file

@ -6,7 +6,7 @@
*/
import { ElasticsearchClient } from 'kibana/server';
import { AgentService } from '../../../../../../fleet/server';
import { AgentClient } from '../../../../../../fleet/server';
import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services';
import { Agent } from '../../../../../../fleet/common/types/models';
import { HostStatus } from '../../../../../common/endpoint/types';
@ -34,7 +34,7 @@ export function buildStatusesKuery(statusesToFilter: string[]): string | undefin
}
export async function findAgentIdsByStatus(
agentService: AgentService,
agentClient: AgentClient,
esClient: ElasticsearchClient,
statuses: string[],
pageSize: number = 1000
@ -59,7 +59,7 @@ export async function findAgentIdsByStatus(
let hasMore = true;
while (hasMore) {
const agents = await agentService.listAgents(esClient, searchOptions(page++));
const agents = await agentClient.listAgents(searchOptions(page++));
result.push(...agents.agents.map((agent: Agent) => agent.id));
hasMore = agents.agents.length > 0;
}

View file

@ -8,9 +8,9 @@
import { ElasticsearchClient } from 'kibana/server';
import { findAllUnenrolledAgentIds } from './unenroll';
import { elasticsearchServiceMock } from '../../../../../../../../src/core/server/mocks';
import { AgentService } from '../../../../../../fleet/server/services';
import { AgentClient } from '../../../../../../fleet/server/services';
import {
createMockAgentService,
createMockAgentClient,
createPackagePolicyServiceMock,
} from '../../../../../../fleet/server/mocks';
import { Agent, PackagePolicy } from '../../../../../../fleet/common/types/models';
@ -18,12 +18,12 @@ import { PackagePolicyServiceInterface } from '../../../../../../fleet/server';
describe('test find all unenrolled Agent id', () => {
let mockElasticsearchClient: jest.Mocked<ElasticsearchClient>;
let mockAgentService: jest.Mocked<AgentService>;
let mockAgentClient: jest.Mocked<AgentClient>;
let mockPackagePolicyService: jest.Mocked<PackagePolicyServiceInterface>;
beforeEach(() => {
mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
mockAgentService = createMockAgentService();
mockAgentClient = createMockAgentClient();
mockPackagePolicyService = createPackagePolicyServiceMock();
});
@ -46,7 +46,7 @@ describe('test find all unenrolled Agent id', () => {
perPage: 10,
page: 1,
});
mockAgentService.listAgents
mockAgentClient.listAgents
.mockImplementationOnce(() =>
Promise.resolve({
agents: [
@ -81,7 +81,7 @@ describe('test find all unenrolled Agent id', () => {
);
const endpointPolicyIds = ['test-endpoint-policy-id'];
const agentIds = await findAllUnenrolledAgentIds(
mockAgentService,
mockAgentClient,
mockElasticsearchClient,
endpointPolicyIds
);
@ -89,7 +89,7 @@ describe('test find all unenrolled Agent id', () => {
expect(agentIds).toBeTruthy();
expect(agentIds).toEqual(['id1', 'id2']);
expect(mockAgentService.listAgents).toHaveBeenNthCalledWith(1, mockElasticsearchClient, {
expect(mockAgentClient.listAgents).toHaveBeenNthCalledWith(1, {
page: 1,
perPage: 1000,
showInactive: true,

View file

@ -6,11 +6,11 @@
*/
import { ElasticsearchClient } from 'kibana/server';
import { AgentService } from '../../../../../../fleet/server';
import type { AgentClient } from '../../../../../../fleet/server';
import { Agent } from '../../../../../../fleet/common/types/models';
export async function findAllUnenrolledAgentIds(
agentService: AgentService,
agentClient: AgentClient,
esClient: ElasticsearchClient,
endpointPolicyIds: string[],
pageSize: number = 1000
@ -41,7 +41,7 @@ export async function findAllUnenrolledAgentIds(
let hasMore = true;
while (hasMore) {
const unenrolledAgents = await agentService.listAgents(esClient, searchOptions(page++));
const unenrolledAgents = await agentClient.listAgents(searchOptions(page++));
result.push(...unenrolledAgents.agents.map((agent: Agent) => agent.id));
hasMore = unenrolledAgents.agents.length > 0;
}

View file

@ -11,7 +11,7 @@ import {
createMockEndpointAppContextServiceStartContract,
createRouteHandlerContext,
} from '../../mocks';
import { createMockAgentService } from '../../../../../fleet/server/mocks';
import { createMockAgentClient, createMockAgentService } from '../../../../../fleet/server/mocks';
import { getHostPolicyResponseHandler, getAgentPolicySummaryHandler } from './handlers';
import {
KibanaResponseFactory,
@ -29,7 +29,7 @@ import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'
import { parseExperimentalConfigValue } from '../../../../common/experimental_features';
import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
import { Agent } from '../../../../../fleet/common/types/models';
import { AgentService } from '../../../../../fleet/server/services';
import { AgentClient, 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';
@ -101,6 +101,7 @@ describe('test policy response handler', () => {
describe('test agent policy summary handler', () => {
let mockAgentService: jest.Mocked<AgentService>;
let mockAgentClient: jest.Mocked<AgentClient>;
let agentListResult: {
agents: Agent[];
@ -122,6 +123,8 @@ describe('test policy response handler', () => {
mockResponse = httpServerMock.createResponseFactory();
endpointAppContextService = new EndpointAppContextService();
mockAgentService = createMockAgentService();
mockAgentClient = createMockAgentClient();
mockAgentService.asScoped.mockReturnValue(mockAgentClient);
emptyAgentListResult = {
agents: [],
total: 2,
@ -173,7 +176,7 @@ describe('test policy response handler', () => {
afterEach(() => endpointAppContextService.stop());
it('should return the summary of all the agent with the given policy name', async () => {
mockAgentService.listAgents
mockAgentClient.listAgents
.mockImplementationOnce(() => Promise.resolve(agentListResult))
.mockImplementationOnce(() => Promise.resolve(emptyAgentListResult));
@ -204,7 +207,7 @@ describe('test policy response handler', () => {
});
it('should return the agent summary', async () => {
mockAgentService.listAgents
mockAgentClient.listAgents
.mockImplementationOnce(() => Promise.resolve(agentListResult))
.mockImplementationOnce(() => Promise.resolve(emptyAgentListResult));

View file

@ -44,6 +44,7 @@ export const getAgentPolicySummaryHandler = function (
endpointAppContext,
context.core.savedObjects.client,
context.core.elasticsearch.client.asCurrentUser,
request,
request.query.package_name,
request.query?.policy_id || undefined
);

View file

@ -8,6 +8,7 @@
import {
ElasticsearchClient,
IScopedClusterClient,
KibanaRequest,
SavedObjectsClientContract,
} from '../../../../../../../src/core/server';
import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types';
@ -78,6 +79,7 @@ export async function getAgentPolicySummary(
endpointAppContext: EndpointAppContext,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
request: KibanaRequest,
packageName: string,
policyId?: string,
pageSize: number = 1000
@ -89,6 +91,7 @@ export async function getAgentPolicySummary(
endpointAppContext,
soClient,
esClient,
request,
`${agentQuery} AND policy_id:${policyId}`,
pageSize
)
@ -96,7 +99,7 @@ export async function getAgentPolicySummary(
}
return transformAgentVersionMap(
await agentVersionsMap(endpointAppContext, soClient, esClient, agentQuery, pageSize)
await agentVersionsMap(endpointAppContext, soClient, esClient, request, agentQuery, pageSize)
);
}
@ -104,6 +107,7 @@ export async function agentVersionsMap(
endpointAppContext: EndpointAppContext,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
request: KibanaRequest,
kqlQuery: string,
pageSize: number = 1000
): Promise<Map<string, number>> {
@ -123,7 +127,8 @@ export async function agentVersionsMap(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const queryResult = await endpointAppContext.service
.getAgentService()!
.listAgents(esClient, searchOptions(page++));
.asScoped(request)
.listAgents(searchOptions(page++));
queryResult.agents.forEach((agent: Agent) => {
const agentVersion = agent.local_metadata?.elastic?.agent?.version;
if (result.has(agentVersion)) {

View file

@ -0,0 +1,90 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { KibanaRequest } from 'kibana/server';
import type {
AgentClient,
AgentPolicyServiceInterface,
FleetStartContract,
PackagePolicyServiceInterface,
PackageService,
} from '../../../../fleet/server';
export interface EndpointFleetServicesFactoryInterface {
asScoped(req: KibanaRequest): EndpointScopedFleetServicesInterface;
asInternalUser(): EndpointInternalFleetServicesInterface;
}
export class EndpointFleetServicesFactory implements EndpointFleetServicesFactoryInterface {
constructor(
private readonly fleetDependencies: Pick<
FleetStartContract,
'agentService' | 'packageService' | 'packagePolicyService' | 'agentPolicyService'
>
) {}
asScoped(req: KibanaRequest): EndpointScopedFleetServicesInterface {
const {
agentPolicyService: agentPolicy,
packagePolicyService: packagePolicy,
agentService,
packageService: packages,
} = this.fleetDependencies;
return {
agent: agentService.asScoped(req),
agentPolicy,
packages,
packagePolicy,
asInternal: this.asInternalUser.bind(this),
};
}
asInternalUser(): EndpointInternalFleetServicesInterface {
const {
agentPolicyService: agentPolicy,
packagePolicyService: packagePolicy,
agentService,
packageService: packages,
} = this.fleetDependencies;
return {
agent: agentService.asInternalUser,
agentPolicy,
packages,
packagePolicy,
asScoped: this.asScoped.bind(this),
};
}
}
/**
* The set of Fleet services used by Endpoint
*/
export interface EndpointFleetServicesInterface {
agent: AgentClient;
agentPolicy: AgentPolicyServiceInterface;
packages: PackageService;
packagePolicy: PackagePolicyServiceInterface;
}
export interface EndpointScopedFleetServicesInterface extends EndpointFleetServicesInterface {
/**
* get internal fleet services instance
*/
asInternal: EndpointFleetServicesFactoryInterface['asInternalUser'];
}
export interface EndpointInternalFleetServicesInterface extends EndpointFleetServicesInterface {
/**
* get scoped endpoint fleet services instance
*/
asScoped: EndpointFleetServicesFactoryInterface['asScoped'];
}

View file

@ -117,12 +117,16 @@ describe('EndpointMetadataService', () => {
it('should throw wrapped error if es error', async () => {
const esMockResponse = elasticsearchServiceMock.createErrorTransportRequestPromise({});
esClient.search.mockResolvedValue(esMockResponse);
const metadataListResponse = metadataService.getHostMetadataList(esClient, {
page: 0,
pageSize: 10,
kuery: '',
hostStatuses: [],
});
const metadataListResponse = metadataService.getHostMetadataList(
esClient,
testMockedContext.fleetServices,
{
page: 0,
pageSize: 10,
kuery: '',
hostStatuses: [],
}
);
await expect(metadataListResponse).rejects.toThrow(EndpointError);
});
@ -176,6 +180,7 @@ describe('EndpointMetadataService', () => {
const queryOptions = { page: 1, pageSize: 10, kuery: '', hostStatuses: [] };
const metadataListResponse = await metadataService.getHostMetadataList(
esClient,
testMockedContext.fleetServices,
queryOptions
);
const unitedIndexQuery = await buildUnitedIndexQuery(queryOptions, packagePolicyIds);

View file

@ -26,7 +26,6 @@ import { Agent, AgentPolicy, PackagePolicy } from '../../../../../fleet/common';
import {
AgentNotFoundError,
AgentPolicyServiceInterface,
AgentService,
PackagePolicyServiceInterface,
} from '../../../../../fleet/server';
import {
@ -57,6 +56,7 @@ import { getAllEndpointPackagePolicies } from '../../routes/metadata/support/end
import { getAgentStatus } from '../../../../../fleet/common/services/agent_status';
import { GetMetadataListRequestQuery } from '../../../../common/endpoint/schema/metadata';
import { EndpointError } from '../../../../common/endpoint/errors';
import { EndpointFleetServicesInterface } from '../endpoint_fleet_services';
type AgentPolicyWithPackagePolicies = Omit<AgentPolicy, 'package_policies'> & {
package_policies: PackagePolicy[];
@ -84,7 +84,6 @@ export class EndpointMetadataService {
constructor(
private savedObjectsStart: SavedObjectsServiceStart,
private readonly agentService: AgentService,
private readonly agentPolicyService: AgentPolicyServiceInterface,
private readonly packagePolicyService: PackagePolicyServiceInterface,
private readonly logger?: Logger
@ -156,12 +155,14 @@ export class EndpointMetadataService {
* Retrieve a single endpoint host metadata along with fleet information
*
* @param esClient Elasticsearch Client (usually scoped to the user's context)
* @param fleetServices
* @param endpointId the endpoint id (from `agent.id`)
*
* @throws
*/
async getEnrichedHostMetadata(
esClient: ElasticsearchClient,
fleetServices: EndpointFleetServicesInterface,
endpointId: string
): Promise<HostInfo> {
const endpointMetadata = await this.getHostMetadata(esClient, endpointId);
@ -176,7 +177,7 @@ export class EndpointMetadataService {
this.logger?.warn(`Missing elastic agent id, using host id instead ${fleetAgentId}`);
}
fleetAgent = await this.getFleetAgent(esClient, fleetAgentId);
fleetAgent = await this.getFleetAgent(fleetServices.agent, fleetAgentId);
} catch (error) {
if (error instanceof FleetAgentNotFoundError) {
this.logger?.warn(`agent with id ${fleetAgentId} not found`);
@ -192,12 +193,12 @@ export class EndpointMetadataService {
);
}
return this.enrichHostMetadata(esClient, endpointMetadata, fleetAgent);
return this.enrichHostMetadata(fleetServices, endpointMetadata, fleetAgent);
}
/**
* Enriches a host metadata document with data from fleet
* @param esClient
* @param fleetServices
* @param endpointMetadata
* @param _fleetAgent
* @param _fleetAgentPolicy
@ -206,7 +207,7 @@ export class EndpointMetadataService {
*/
// eslint-disable-next-line complexity
private async enrichHostMetadata(
esClient: ElasticsearchClient,
fleetServices: EndpointFleetServicesInterface,
endpointMetadata: HostMetadata,
/**
* If undefined, it will be retrieved from Fleet using the ID in the endpointMetadata.
@ -242,7 +243,7 @@ export class EndpointMetadataService {
);
}
fleetAgent = await this.getFleetAgent(esClient, fleetAgentId);
fleetAgent = await this.getFleetAgent(fleetServices.agent, fleetAgentId);
} catch (error) {
if (error instanceof FleetAgentNotFoundError) {
this.logger?.warn(`agent with id ${fleetAgentId} not found`);
@ -310,12 +311,15 @@ export class EndpointMetadataService {
/**
* Retrieve a single Fleet Agent data
*
* @param esClient Elasticsearch Client (usually scoped to the user's context)
* @param fleetAgentService
* @param agentId The elastic agent id (`from `elastic.agent.id`)
*/
async getFleetAgent(esClient: ElasticsearchClient, agentId: string): Promise<Agent> {
async getFleetAgent(
fleetAgentService: EndpointFleetServicesInterface['agent'],
agentId: string
): Promise<Agent> {
try {
return await this.agentService.getAgent(esClient, agentId);
return await fleetAgentService.getAgent(agentId);
} catch (error) {
if (error instanceof AgentNotFoundError) {
throw new FleetAgentNotFoundError(`agent with id ${agentId} not found`, error);
@ -402,6 +406,7 @@ export class EndpointMetadataService {
*/
async getHostMetadataList(
esClient: ElasticsearchClient,
fleetServices: EndpointFleetServicesInterface,
queryOptions: GetMetadataListRequestQuery
): Promise<Pick<MetadataListResponse, 'data' | 'total'>> {
const endpointPolicies = await getAllEndpointPackagePolicies(
@ -468,7 +473,7 @@ export class EndpointMetadataService {
const endpointPolicy = endpointPoliciesMap[agent.policy_id!];
hosts.push(
await this.enrichHostMetadata(esClient, metadata, agent, agentPolicy, endpointPolicy)
await this.enrichHostMetadata(fleetServices, metadata, agent, agentPolicy, endpointPolicy)
);
}
}

View file

@ -11,9 +11,14 @@ import { savedObjectsServiceMock } from '../../../../../../../src/core/server/mo
import {
createMockAgentPolicyService,
createMockAgentService,
createMockPackageService,
createPackagePolicyServiceMock,
} from '../../../../../fleet/server/mocks';
import { AgentPolicyServiceInterface, AgentService } from '../../../../../fleet/server';
import {
EndpointFleetServicesFactory,
EndpointFleetServicesInterface,
} from '../endpoint_fleet_services';
const createCustomizedPackagePolicyService = () => {
const service = createPackagePolicyServiceMock();
@ -38,6 +43,7 @@ export interface EndpointMetadataServiceTestContextMock {
agentPolicyService: jest.Mocked<AgentPolicyServiceInterface>;
packagePolicyService: ReturnType<typeof createPackagePolicyServiceMock>;
endpointMetadataService: EndpointMetadataService;
fleetServices: EndpointFleetServicesInterface;
}
export const createEndpointMetadataServiceTestContextMock = (
@ -46,11 +52,18 @@ export const createEndpointMetadataServiceTestContextMock = (
agentPolicyService: jest.Mocked<AgentPolicyServiceInterface> = createMockAgentPolicyService(),
packagePolicyService: ReturnType<
typeof createPackagePolicyServiceMock
> = createCustomizedPackagePolicyService()
> = createCustomizedPackagePolicyService(),
packageService: ReturnType<typeof createMockPackageService> = createMockPackageService()
): EndpointMetadataServiceTestContextMock => {
const fleetServices = new EndpointFleetServicesFactory({
agentService,
packageService,
packagePolicyService,
agentPolicyService,
}).asInternalUser();
const endpointMetadataService = new EndpointMetadataService(
savedObjectsStart,
agentService,
agentPolicyService,
packagePolicyService
);
@ -61,5 +74,6 @@ export const createEndpointMetadataServiceTestContextMock = (
agentPolicyService,
packagePolicyService,
endpointMetadataService,
fleetServices,
};
};

View file

@ -6,12 +6,14 @@
*/
import { coreMock } from '../../../../src/core/server/mocks';
import { createFleetRequestHandlerContextMock } from '../../fleet/server/mocks';
import { licensingMock } from '../../licensing/server/mocks';
function createCoreRequestHandlerContextMock() {
return {
core: coreMock.createRequestHandlerContext(),
licensing: licensingMock.createRequestHandlerContext(),
fleet: createFleetRequestHandlerContextMock(),
};
}

View file

@ -13,7 +13,7 @@ import {
} from 'src/core/server';
import { SearchRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { getTrustedAppsList } from '../../endpoint/routes/trusted_apps/service';
import { AgentService, AgentPolicyServiceInterface } from '../../../../fleet/server';
import { AgentClient, AgentPolicyServiceInterface } from '../../../../fleet/server';
import { ExceptionListClient } from '../../../../lists/server';
import { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services';
import { TELEMETRY_MAX_BUFFER_SIZE } from './constants';
@ -32,7 +32,7 @@ import {
export class TelemetryReceiver {
private readonly logger: Logger;
private agentService?: AgentService;
private agentClient?: AgentClient;
private agentPolicyService?: AgentPolicyServiceInterface;
private esClient?: ElasticsearchClient;
private exceptionListClient?: ExceptionListClient;
@ -52,7 +52,7 @@ export class TelemetryReceiver {
exceptionListClient?: ExceptionListClient
) {
this.kibanaIndex = kibanaIndex;
this.agentService = endpointContextService?.getAgentService();
this.agentClient = endpointContextService?.getAgentService()?.asInternalUser;
this.agentPolicyService = endpointContextService?.getAgentPolicyService();
this.esClient = core?.elasticsearch.client.asInternalUser;
this.exceptionListClient = exceptionListClient;
@ -70,7 +70,7 @@ export class TelemetryReceiver {
throw Error('elasticsearch client is unavailable: cannot retrieve fleet policy responses');
}
return this.agentService?.listAgents(this.esClient, {
return this.agentClient?.listAgents({
perPage: this.max_records,
showInactive: true,
sortField: 'enrolled_at',

View file

@ -402,8 +402,6 @@ export class Plugin implements ISecuritySolutionPlugin {
endpointMetadataService: new EndpointMetadataService(
core.savedObjects,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
plugins.fleet?.agentService!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
plugins.fleet?.agentPolicyService!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
plugins.fleet?.packagePolicyService!,

View file

@ -9,6 +9,7 @@ import { set } from '@elastic/safer-lodash-set/fp';
import { get, has, head } from 'lodash/fp';
import {
IScopedClusterClient,
KibanaRequest,
SavedObjectsClientContract,
} from '../../../../../../../../../src/core/server';
import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields';
@ -180,35 +181,32 @@ export const getHostEndpoint = async (
esClient: IScopedClusterClient;
savedObjectsClient: SavedObjectsClientContract;
endpointContext: EndpointAppContext;
request: KibanaRequest;
}
): Promise<EndpointFields | null> => {
if (!id) {
return null;
}
const { esClient, endpointContext } = deps;
const { esClient, endpointContext, request } = deps;
const logger = endpointContext.logFactory.get('metadata');
try {
const agentService = endpointContext.service.getAgentService();
const fleetServices = endpointContext.service.getScopedFleetServices(request);
const endpointMetadataService = endpointContext.service.getEndpointMetadataService();
if (!agentService) {
throw new Error('agentService not available');
}
const endpointData = await endpointContext.service
.getEndpointMetadataService()
const endpointData = await endpointMetadataService
// Using `internalUser` ES client below due to the fact that Fleet data has been moved to
// system indices (`.fleet*`). Because this is a readonly action, this should be ok to do
// here until proper RBOC controls are implemented
.getEnrichedHostMetadata(esClient.asInternalUser, id);
.getEnrichedHostMetadata(esClient.asInternalUser, fleetServices, id);
const fleetAgentId = endpointData.metadata.elastic.agent.id;
const pendingActions = fleetAgentId
? getPendingActionCounts(
esClient.asInternalUser,
endpointContext.service.getEndpointMetadataService(),
endpointMetadataService,
[fleetAgentId],
endpointContext.experimentalFeatures.pendingActionResponsesWithAck
)

View file

@ -14,6 +14,7 @@ import {
} from './__mocks__';
import {
IScopedClusterClient,
KibanaRequest,
SavedObjectsClientContract,
} from '../../../../../../../../../src/core/server';
import { EndpointAppContext } from '../../../../../endpoint/types';
@ -35,6 +36,7 @@ const mockDeps = {
},
service: {} as EndpointAppContextService,
} as EndpointAppContext,
request: {} as KibanaRequest,
};
describe('hostDetails search strategy', () => {

View file

@ -23,6 +23,7 @@ import { formatHostItem, getHostEndpoint } from './helpers';
import { EndpointAppContext } from '../../../../../endpoint/types';
import {
IScopedClusterClient,
KibanaRequest,
SavedObjectsClientContract,
} from '../../../../../../../../../src/core/server';
@ -35,6 +36,7 @@ export const hostDetails: SecuritySolutionFactory<HostsQueries.details> = {
esClient: IScopedClusterClient;
savedObjectsClient: SavedObjectsClientContract;
endpointContext: EndpointAppContext;
request: KibanaRequest;
}
): Promise<HostDetailsStrategyResponse> => {
const aggregations = get('aggregations', response.rawResponse);

View file

@ -7,6 +7,7 @@
import type {
IScopedClusterClient,
KibanaRequest,
SavedObjectsClientContract,
} from '../../../../../../../src/core/server';
import type {
@ -29,6 +30,7 @@ export interface SecuritySolutionFactory<T extends FactoryQueryTypes> {
esClient: IScopedClusterClient;
savedObjectsClient: SavedObjectsClientContract;
endpointContext: EndpointAppContext;
request: KibanaRequest;
}
) => Promise<StrategyResponseType<T>>;
}

View file

@ -49,6 +49,7 @@ export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTyp
esClient: deps.esClient,
savedObjectsClient: deps.savedObjectsClient,
endpointContext,
request: deps.request,
})
)
);

View file

@ -8,6 +8,7 @@
import type { IRouter, RequestHandlerContext } from 'src/core/server';
import type { ActionsApiRequestHandlerContext } from '../../actions/server';
import type { AlertingApiRequestHandlerContext } from '../../alerting/server';
import type { FleetRequestHandlerContext } from '../../fleet/server';
import type { LicensingApiRequestHandlerContext } from '../../licensing/server';
import type { ListsApiRequestHandlerContext, ExceptionListClient } from '../../lists/server';
import type { IRuleDataService } from '../../rule_registry/server';
@ -35,6 +36,7 @@ export interface SecuritySolutionRequestHandlerContext extends RequestHandlerCon
alerting: AlertingApiRequestHandlerContext;
licensing: LicensingApiRequestHandlerContext;
lists?: ListsApiRequestHandlerContext;
fleet?: FleetRequestHandlerContext['fleet'];
}
export type SecuritySolutionPluginRouter = IRouter<SecuritySolutionRequestHandlerContext>;