From feb120247df2b1337064946c03d7d22f7d3d7ca1 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Mon, 6 Jul 2020 19:21:07 -0400 Subject: [PATCH] Make ingestManager optional for security-solution startup --- x-pack/plugins/security_solution/kibana.json | 2 +- .../endpoint_app_context_services.test.ts | 4 +- .../endpoint/endpoint_app_context_services.ts | 16 ++-- .../server/endpoint/routes/metadata/index.ts | 76 ++++++++++--------- .../endpoint/routes/metadata/metadata.test.ts | 7 +- .../security_solution/server/plugin.ts | 40 ++++++---- 6 files changed, 80 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index f6f2d5171312cc..745df54c621585 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -11,7 +11,6 @@ "embeddable", "features", "home", - "ingestManager", "taskManager", "inspector", "licensing", @@ -21,6 +20,7 @@ ], "optionalPlugins": [ "encryptedSavedObjects", + "ingestManager", "ml", "newsfeed", "security", diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.test.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.test.ts index 2daf259941cbfb..22ea0896fec817 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.test.ts @@ -8,11 +8,11 @@ import { httpServerMock } from '../../../../../src/core/server/mocks'; import { EndpointAppContextService } from './endpoint_app_context_services'; describe('test endpoint app context services', () => { - it('should throw error on getAgentService if start is not called', async () => { + it('should return undefined on getAgentService if dependencies are not enabled', async () => { const endpointAppContextService = new EndpointAppContextService(); expect(() => endpointAppContextService.getAgentService()).toThrow(Error); }); - it('should return undefined on getManifestManager if start is not called', async () => { + it('should return undefined on getManifestManager if dependencies are not enabled', async () => { const endpointAppContextService = new EndpointAppContextService(); expect(endpointAppContextService.getManifestManager()).toEqual(undefined); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 97a82049634c40..5d3fa24e8f51c0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -12,12 +12,11 @@ import { AgentService, IngestManagerStartContract } from '../../../ingest_manage import { getPackageConfigCreateCallback } from './ingest_integration'; import { ManifestManager } from './services/artifacts'; -export type EndpointAppContextServiceStartContract = Pick< - IngestManagerStartContract, - 'agentService' +export type EndpointAppContextServiceStartContract = Partial< + Pick > & { - manifestManager?: ManifestManager | undefined; - registerIngestCallback: IngestManagerStartContract['registerExternalCallback']; + manifestManager?: ManifestManager; + registerIngestCallback?: IngestManagerStartContract['registerExternalCallback']; savedObjectsStart: SavedObjectsServiceStart; }; @@ -35,7 +34,7 @@ export class EndpointAppContextService { this.manifestManager = dependencies.manifestManager; this.savedObjectsStart = dependencies.savedObjectsStart; - if (this.manifestManager !== undefined) { + if (this.manifestManager && dependencies.registerIngestCallback) { dependencies.registerIngestCallback( 'packageConfigCreate', getPackageConfigCreateCallback(this.manifestManager) @@ -45,10 +44,7 @@ export class EndpointAppContextService { public stop() {} - public getAgentService(): AgentService { - if (!this.agentService) { - throw new Error(`must call start on ${EndpointAppContextService.name} to call getter`); - } + public getAgentService(): AgentService | undefined { return this.agentService; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index 235e7152b83cf1..45bd3544304dd7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -18,6 +18,7 @@ import { HostStatus, } from '../../../../common/endpoint/types'; import { EndpointAppContext } from '../../types'; +import { AgentService } from '../../../../../ingest_manager/server'; import { Agent, AgentStatus } from '../../../../../ingest_manager/common/types/models'; import { findAllUnenrolledAgentIds } from './support/unenroll'; @@ -26,8 +27,9 @@ interface HitSource { } interface MetadataRequestContext { + agentService: AgentService; + logger: Logger; requestHandlerContext: RequestHandlerContext; - endpointAppContext: EndpointAppContext; } const HOST_STATUS_MAPPING = new Map([ @@ -35,8 +37,12 @@ const HOST_STATUS_MAPPING = new Map([ ['offline', HostStatus.OFFLINE], ]); +const getLogger = (endpointAppContext: EndpointAppContext): Logger => { + return endpointAppContext.logFactory.get('metadata'); +}; + export function registerEndpointRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { - const logger = endpointAppContext.logFactory.get('metadata'); + const logger = getLogger(endpointAppContext); router.post( { path: '/api/endpoint/metadata', @@ -66,12 +72,23 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp }) ), }, - options: { authRequired: true }, + options: { authRequired: true, tags: ['access:securitySolution'] }, }, async (context, req, res) => { + const agentService = endpointAppContext.service.getAgentService(); + if (agentService === undefined) { + return res.internalError({ body: 'agentService not available' }); + } + + const metadataRequestContext: MetadataRequestContext = { + agentService, + logger, + requestHandlerContext: context, + }; + try { const unenrolledAgentIds = await findAllUnenrolledAgentIds( - endpointAppContext.service.getAgentService(), + agentService, context.core.savedObjects.client ); @@ -89,10 +106,7 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp queryParams )) as SearchResponse; return res.ok({ - body: await mapToHostResultList(queryParams, response, { - endpointAppContext, - requestHandlerContext: context, - }), + body: await mapToHostResultList(queryParams, response, metadataRequestContext), }); } catch (err) { logger.warn(JSON.stringify(err, null, 2)); @@ -107,17 +121,16 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp validate: { params: schema.object({ id: schema.string() }), }, - options: { authRequired: true }, + options: { authRequired: true, tags: ['access:securitySolution'] }, }, async (context, req, res) => { + const agentService = endpointAppContext.service.getAgentService(); + if (agentService === undefined) { + return res.internalError({ body: 'agentService not available' }); + } + try { - const doc = await getHostData( - { - endpointAppContext, - requestHandlerContext: context, - }, - req.params.id - ); + const doc = await getHostData(context, agentService, req.params.id); if (doc) { return res.ok({ body: doc }); } @@ -164,17 +177,16 @@ async function findAgent( metadataRequestContext: MetadataRequestContext, hostMetadata: HostMetadata ): Promise { - const logger = metadataRequestContext.endpointAppContext.logFactory.get('metadata'); try { - return await metadataRequestContext.endpointAppContext.service - .getAgentService() - .getAgent( - metadataRequestContext.requestHandlerContext.core.savedObjects.client, - hostMetadata.elastic.agent.id - ); + return await metadataRequestContext.agentService.getAgent( + metadataRequestContext.requestHandlerContext.core.savedObjects.client, + hostMetadata.elastic.agent.id + ); } catch (e) { if (e.isBoom && e.output.statusCode === 404) { - logger.warn(`agent with id ${hostMetadata.elastic.agent.id} not found`); + metadataRequestContext.logger.warn( + `agent with id ${hostMetadata.elastic.agent.id} not found` + ); return undefined; } else { throw e; @@ -217,7 +229,7 @@ async function enrichHostMetadata( ): Promise { let hostStatus = HostStatus.ERROR; let elasticAgentId = hostMetadata?.elastic?.agent?.id; - const log = logger(metadataRequestContext.endpointAppContext); + const log = metadataRequestContext.logger; try { /** * Get agent status by elastic agent id if available or use the host id. @@ -228,12 +240,10 @@ async function enrichHostMetadata( log.warn(`Missing elastic agent id, using host id instead ${elasticAgentId}`); } - const status = await metadataRequestContext.endpointAppContext.service - .getAgentService() - .getAgentStatusById( - metadataRequestContext.requestHandlerContext.core.savedObjects.client, - elasticAgentId - ); + const status = await metadataRequestContext.agentService.getAgentStatusById( + metadataRequestContext.requestHandlerContext.core.savedObjects.client, + elasticAgentId + ); hostStatus = HOST_STATUS_MAPPING.get(status) || HostStatus.ERROR; } catch (e) { if (e.isBoom && e.output.statusCode === 404) { @@ -248,7 +258,3 @@ async function enrichHostMetadata( host_status: hostStatus, }; } - -const logger = (endpointAppContext: EndpointAppContext): Logger => { - return endpointAppContext.logFactory.get('metadata'); -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 668911b8d1f296..4a1b0f024474b9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -47,8 +47,9 @@ describe('test endpoint route', () => { let routeHandler: RequestHandler; // eslint-disable-next-line @typescript-eslint/no-explicit-any let routeConfig: RouteConfig; - let mockAgentService: ReturnType< - typeof createMockEndpointAppContextServiceStartContract + // tests assume that ingestManager is enabled, and thus agentService is available + let mockAgentService: Required< + ReturnType >['agentService']; let endpointAppContextService: EndpointAppContextService; const noUnenrolledAgent = { @@ -70,7 +71,7 @@ describe('test endpoint route', () => { endpointAppContextService = new EndpointAppContextService(); const startContract = createMockEndpointAppContextServiceStartContract(); endpointAppContextService.start(startContract); - mockAgentService = startContract.agentService; + mockAgentService = startContract.agentService!; registerEndpointRoutes(routerMock, { logFactory: loggingSystemMock.create(), diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a97f1eee56342c..ddc32f2959fbb6 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -24,7 +24,7 @@ import { ListPluginSetup } from '../../lists/server'; import { EncryptedSavedObjectsPluginSetup as EncryptedSavedObjectsSetup } from '../../encrypted_saved_objects/server'; import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server'; import { LicensingPluginSetup } from '../../licensing/server'; -import { IngestManagerStartContract } from '../../ingest_manager/server'; +import { IngestManagerStartContract, ExternalCallback } from '../../ingest_manager/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { initServer } from './init_server'; import { compose } from './lib/compose/kibana'; @@ -55,14 +55,14 @@ export interface SetupPlugins { licensing: LicensingPluginSetup; security?: SecuritySetup; spaces?: SpacesSetup; - taskManager: TaskManagerSetupContract; + taskManager?: TaskManagerSetupContract; ml?: MlSetup; lists?: ListPluginSetup; } export interface StartPlugins { - ingestManager: IngestManagerStartContract; - taskManager: TaskManagerStartContract; + ingestManager?: IngestManagerStartContract; + taskManager?: TaskManagerStartContract; } // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -224,11 +224,15 @@ export class Plugin implements IPlugin { + return plugins.taskManager && plugins.lists; + }; + + if (exceptionListsSetupEnabled()) { this.lists = plugins.lists; this.manifestTask = new ManifestTask({ endpointAppContext: endpointContext, - taskManager: plugins.taskManager, + taskManager: plugins.taskManager!, }); } @@ -242,32 +246,40 @@ export class Plugin implements IPlugin void) | undefined; + + const exceptionListsStartEnabled = () => { + return this.lists && plugins.taskManager && plugins.ingestManager; + }; + + if (exceptionListsStartEnabled()) { + const exceptionListClient = this.lists!.getExceptionListClient(savedObjectsClient, 'kibana'); const artifactClient = new ArtifactClient(savedObjectsClient); + + registerIngestCallback = plugins.ingestManager!.registerExternalCallback; manifestManager = new ManifestManager({ savedObjectsClient, artifactClient, exceptionListClient, - packageConfigService: plugins.ingestManager.packageConfigService, + packageConfigService: plugins.ingestManager!.packageConfigService, logger: this.logger, cache: this.exceptionsCache, }); } this.endpointAppContextService.start({ - agentService: plugins.ingestManager.agentService, + agentService: plugins.ingestManager?.agentService, manifestManager, - registerIngestCallback: plugins.ingestManager.registerExternalCallback, + registerIngestCallback, savedObjectsStart: core.savedObjects, }); - if (this.manifestTask) { + if (exceptionListsStartEnabled() && this.manifestTask) { this.manifestTask.start({ - taskManager: plugins.taskManager, + taskManager: plugins.taskManager!, }); } else { - this.logger.debug('Manifest task not available.'); + this.logger.debug('User artifacts task not available.'); } return {};