diff --git a/x-pack/plugins/cloud_defend/server/plugin.test.ts b/x-pack/plugins/cloud_defend/server/plugin.test.ts index 8f2b7fd9998fc1..3df0ae04f812a3 100644 --- a/x-pack/plugins/cloud_defend/server/plugin.test.ts +++ b/x-pack/plugins/cloud_defend/server/plugin.test.ts @@ -11,29 +11,16 @@ import { httpServerMock, savedObjectsClientMock, } from '@kbn/core/server/mocks'; -import { - createPackagePolicyServiceMock, - createArtifactsClientMock, - createMockPackageService, - createMockAgentService, - createMockAgentPolicyService, -} from '@kbn/fleet-plugin/server/mocks'; import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { CloudDefendPlugin } from './plugin'; import { CloudDefendPluginStartDeps } from './types'; -import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; import { PackagePolicy, UpdatePackagePolicy } from '@kbn/fleet-plugin/common'; -import { - ExternalCallback, - FleetStartContract, - PostPackagePolicyPostCreateCallback, -} from '@kbn/fleet-plugin/server'; +import { PostPackagePolicyPostCreateCallback } from '@kbn/fleet-plugin/server'; import { INTEGRATION_PACKAGE_NAME } from '../common/constants'; import Chance from 'chance'; import type { AwaitedProperties } from '@kbn/utility-types'; -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import { ElasticsearchClient, RequestHandlerContext, @@ -42,6 +29,7 @@ import { import { securityMock } from '@kbn/security-plugin/server/mocks'; import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import * as onPackagePolicyPostCreateCallback from './lib/fleet_util'; +import { createFleetStartContractMock } from '@kbn/fleet-plugin/server/mocks'; const chance = new Chance(); @@ -49,26 +37,9 @@ const mockRouteContext = { core: coreMock.createRequestHandlerContext(), } as unknown as AwaitedProperties; -const createMockFleetStartContract = (): DeeplyMockedKeys => { - return { - authz: { - fromRequest: jest.fn(async (_) => createFleetAuthzMock()), - }, - fleetSetupCompleted: jest.fn().mockResolvedValue(undefined), - // @ts-expect-error 2322 - agentService: createMockAgentService(), - // @ts-expect-error 2322 - packageService: createMockPackageService(), - agentPolicyService: createMockAgentPolicyService(), - registerExternalCallback: jest.fn((..._: ExternalCallback) => {}), - packagePolicyService: createPackagePolicyServiceMock(), - createArtifactsClient: jest.fn().mockReturnValue(createArtifactsClientMock()), - }; -}; - describe('Cloud Defend Plugin', () => { describe('start()', () => { - const fleetMock = createMockFleetStartContract(); + const fleetMock = createFleetStartContractMock(); const mockPlugins: CloudDefendPluginStartDeps = { fleet: fleetMock, data: dataPluginMock.createStartContract(), diff --git a/x-pack/plugins/cloud_defend/tsconfig.json b/x-pack/plugins/cloud_defend/tsconfig.json index 2e71cfde9128d7..9b87f53f7c7b62 100755 --- a/x-pack/plugins/cloud_defend/tsconfig.json +++ b/x-pack/plugins/cloud_defend/tsconfig.json @@ -32,7 +32,6 @@ "@kbn/es-types", "@kbn/data-views-plugin", "@kbn/utility-types", - "@kbn/utility-types-jest", "@kbn/kubernetes-security-plugin", "@kbn/core-http-router-server-mocks", "@kbn/core-elasticsearch-server", diff --git a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts index 9707bc98649096..9171fd8d0e8ece 100644 --- a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts @@ -11,20 +11,13 @@ import { httpServerMock, savedObjectsClientMock, } from '@kbn/core/server/mocks'; -import { - createPackagePolicyServiceMock, - createArtifactsClientMock, - createMockPackageService, - createMockAgentService, - createMockAgentPolicyService, -} from '@kbn/fleet-plugin/server/mocks'; +import { createFleetStartContractMock } from '@kbn/fleet-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { createPackagePolicyMock, deletePackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { CspPlugin } from './plugin'; import { CspServerPluginStartDeps } from './types'; -import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; import { Installation, ListResult, @@ -32,15 +25,12 @@ import { UpdatePackagePolicy, } from '@kbn/fleet-plugin/common'; import { - FleetStartContract, PostPackagePolicyPostDeleteCallback, PostPackagePolicyPostCreateCallback, - ExternalCallback, } from '@kbn/fleet-plugin/server'; import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../common/constants'; import Chance from 'chance'; import type { AwaitedProperties } from '@kbn/utility-types'; -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import { createIndexPatternsStartMock } from '@kbn/data-views-plugin/server/mocks'; import { ElasticsearchClient, @@ -56,26 +46,9 @@ const mockRouteContext = { core: coreMock.createRequestHandlerContext(), } as unknown as AwaitedProperties; -const createMockFleetStartContract = (): DeeplyMockedKeys => { - return { - authz: { - fromRequest: jest.fn(async (_) => createFleetAuthzMock()), - }, - fleetSetupCompleted: jest.fn().mockResolvedValue(undefined), - // @ts-expect-error 2322 - agentService: createMockAgentService(), - // @ts-expect-error 2322 - packageService: createMockPackageService(), - agentPolicyService: createMockAgentPolicyService(), - registerExternalCallback: jest.fn((..._: ExternalCallback) => {}), - packagePolicyService: createPackagePolicyServiceMock(), - createArtifactsClient: jest.fn().mockReturnValue(createArtifactsClientMock()), - }; -}; - describe('Cloud Security Posture Plugin', () => { describe('start()', () => { - const fleetMock = createMockFleetStartContract(); + const fleetMock = createFleetStartContractMock(); const mockPlugins: CspServerPluginStartDeps = { fleet: fleetMock, data: dataPluginMock.createStartContract(), diff --git a/x-pack/plugins/cloud_security_posture/tsconfig.json b/x-pack/plugins/cloud_security_posture/tsconfig.json index 2b075062b38c9e..4e43b6df2485ef 100755 --- a/x-pack/plugins/cloud_security_posture/tsconfig.json +++ b/x-pack/plugins/cloud_security_posture/tsconfig.json @@ -38,7 +38,6 @@ "@kbn/monaco", "@kbn/utility-types", "@kbn/core-logging-server-mocks", - "@kbn/utility-types-jest", "@kbn/securitysolution-es-utils", "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-elasticsearch-server", diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 7b11654f8def40..17f85cd252c2b5 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -22,9 +22,17 @@ import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; + +import { createFleetActionsClientMock } from '../services/actions/mocks'; + +import { createFleetFilesClientFactoryMock } from '../services/files/mocks'; + +import { createArtifactsClientMock } from '../services/artifacts/mocks'; + import type { PackagePolicyClient } from '../services/package_policy_service'; import type { AgentPolicyServiceInterface } from '../services'; -import type { FleetAppContext } from '../plugin'; +import type { FleetAppContext, FleetStartContract } from '../plugin'; import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; import type { FleetConfigType } from '../../common/types'; import type { ExperimentalFeatures } from '../../common/experimental_features'; @@ -35,6 +43,8 @@ import { packageServiceMock } from '../services/epm/package_service.mock'; import type { UninstallTokenServiceInterface } from '../services/security/uninstall_token_service'; import type { MessageSigningServiceInterface } from '../services/security'; +import { getPackageSpecTagId } from '../services/epm/kibana/assets/tag_assets'; + import { PackagePolicyMocks } from './package_policy.mocks'; // Export all mocks from artifacts @@ -238,7 +248,7 @@ export const createMockAgentClient = () => agentServiceMock.createClient(); */ export const createMockPackageService = () => packageServiceMock.create(); -export function createMessageSigningServiceMock(): MessageSigningServiceInterface { +export function createMessageSigningServiceMock(): jest.Mocked { return { isEncryptionAvailable: true, generateKeyPair: jest.fn(), @@ -253,7 +263,7 @@ export function createMessageSigningServiceMock(): MessageSigningServiceInterfac }; } -export function createUninstallTokenServiceMock(): UninstallTokenServiceInterface { +export function createUninstallTokenServiceMock(): DeeplyMockedKeys { return { getToken: jest.fn(), getTokenMetadata: jest.fn(), @@ -269,3 +279,27 @@ export function createUninstallTokenServiceMock(): UninstallTokenServiceInterfac scoped: jest.fn().mockImplementation(() => createUninstallTokenServiceMock()), }; } + +export const createFleetStartContractMock = (): DeeplyMockedKeys => { + const fleetAuthzMock = createFleetAuthzMock(); + const fleetArtifactsClient = createArtifactsClientMock(); + const fleetActionsClient = createFleetActionsClientMock(); + + const startContract: DeeplyMockedKeys = { + fleetSetupCompleted: jest.fn(async () => {}), + authz: { fromRequest: jest.fn(async (_) => fleetAuthzMock) }, + packageService: createMockPackageService(), + agentService: createMockAgentService(), + packagePolicyService: createPackagePolicyServiceMock(), + agentPolicyService: createMockAgentPolicyService(), + registerExternalCallback: jest.fn(), + createArtifactsClient: jest.fn((_) => fleetArtifactsClient), + createFilesClient: createFleetFilesClientFactoryMock(), + messageSigningService: createMessageSigningServiceMock(), + uninstallTokenService: createUninstallTokenServiceMock(), + createFleetActionsClient: jest.fn((_) => fleetActionsClient), + getPackageSpecTagId: jest.fn(getPackageSpecTagId), + }; + + return startContract; +}; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 21c3f1bf97f126..c767424cef36a9 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -72,6 +72,7 @@ import { AGENT_POLICY_SAVED_OBJECT_TYPE, LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, } from '../common/constants'; + import { getFilesClientFactory } from './services/files/get_files_client_factory'; import type { MessageSigningServiceInterface } from './services/security'; @@ -98,7 +99,12 @@ import { registerEncryptedSavedObjects, registerSavedObjects } from './saved_obj import { registerRoutes } from './routes'; import type { ExternalCallback, FleetRequestHandlerContext } from './types'; -import type { AgentPolicyServiceInterface, AgentService, PackageService } from './services'; +import type { + AgentPolicyServiceInterface, + AgentService, + ArtifactsClientInterface, + PackageService, +} from './services'; import { agentPolicyService, AgentServiceImpl, @@ -236,7 +242,7 @@ export interface FleetStartContract { * Create a Fleet Artifact Client instance * @param packageName */ - createArtifactsClient: (packageName: string) => FleetArtifactsClient; + createArtifactsClient: (packageName: string) => ArtifactsClientInterface; /** * Create a Fleet Files client instance diff --git a/x-pack/plugins/fleet/server/services/agents/agent_service.mock.ts b/x-pack/plugins/fleet/server/services/agents/agent_service.mock.ts index 9bebdc4c1b24aa..d6d6922e1bdf9c 100644 --- a/x-pack/plugins/fleet/server/services/agents/agent_service.mock.ts +++ b/x-pack/plugins/fleet/server/services/agents/agent_service.mock.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; + import type { AgentClient, AgentService } from './agent_service'; const createClientMock = (): jest.Mocked => ({ @@ -15,7 +17,7 @@ const createClientMock = (): jest.Mocked => ({ getLatestAgentAvailableVersion: jest.fn(), }); -const createServiceMock = (): jest.Mocked => ({ +const createServiceMock = (): DeeplyMockedKeys => ({ asInternalUser: createClientMock(), asScoped: jest.fn().mockReturnValue(createClientMock()), }); diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts index 4e5d8c93f06436..166420c9aee436 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -12,6 +12,8 @@ import { errors } from '@elastic/elasticsearch'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import type { SearchHit, ESSearchResponse } from '@kbn/es-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; + import type { Artifact, ArtifactElasticsearchProperties, @@ -20,7 +22,7 @@ import type { } from './types'; import { newArtifactToElasticsearchProperties } from './mappings'; -export const createArtifactsClientMock = (): jest.Mocked => { +export const createArtifactsClientMock = (): DeeplyMockedKeys => { return { getArtifact: jest.fn().mockResolvedValue(generateArtifactMock()), createArtifact: jest.fn().mockResolvedValue(generateArtifactMock()), diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts index e07b131a9e8061..39d0451687de5f 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.mock.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; + import type { PackageClient, PackageService } from './package_service'; const createClientMock = (): jest.Mocked => ({ @@ -21,8 +23,8 @@ const createClientMock = (): jest.Mocked => ({ reinstallEsAssets: jest.fn(), }); -const createServiceMock = (): PackageService => ({ - asScoped: jest.fn(createClientMock), +const createServiceMock = (): DeeplyMockedKeys => ({ + asScoped: jest.fn((_) => createClientMock()), asInternalUser: createClientMock(), }); diff --git a/x-pack/plugins/fleet/server/services/files/mocks.ts b/x-pack/plugins/fleet/server/services/files/mocks.ts index 2000f8eefc02b4..091dc207b33bc5 100644 --- a/x-pack/plugins/fleet/server/services/files/mocks.ts +++ b/x-pack/plugins/fleet/server/services/files/mocks.ts @@ -9,7 +9,10 @@ import { Readable } from 'stream'; import type { estypes } from '@elastic/elasticsearch'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; + import type { + FilesClientFactory, FleetFile, FleetFromHostFileClientInterface, FleetToHostFileClientInterface, @@ -153,3 +156,10 @@ export const createHapiReadableStreamMock = (): HapiReadableStream => { return readable; }; + +export const createFleetFilesClientFactoryMock = (): DeeplyMockedKeys => { + return { + toHost: jest.fn((_) => createFleetToHostFilesClientMock()), + fromHost: jest.fn((_) => createFleetFromHostFilesClientMock()), + }; +}; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/source_maps/schedule_source_map_migration.ts b/x-pack/plugins/observability_solution/apm/server/routes/source_maps/schedule_source_map_migration.ts index b581a30665a21a..2fbfc65fcce039 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/source_maps/schedule_source_map_migration.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/source_maps/schedule_source_map_migration.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { FleetStartContract } from '@kbn/fleet-plugin/server'; -import { FleetArtifactsClient } from '@kbn/fleet-plugin/server/services'; +import { ArtifactsClientInterface } from '@kbn/fleet-plugin/server/services'; import { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { CoreStart, Logger } from '@kbn/core/server'; import { getApmArtifactClient } from '../fleet/source_maps'; @@ -141,7 +141,7 @@ async function getArtifactsForPage({ kuery, }: { page: number; - apmArtifactClient: FleetArtifactsClient; + apmArtifactClient: ArtifactsClientInterface; kuery: string; }) { return await apmArtifactClient.listArtifacts({ @@ -163,7 +163,7 @@ async function paginateArtifacts({ }: { taskState?: TaskState; page: number; - apmArtifactClient: FleetArtifactsClient; + apmArtifactClient: ArtifactsClientInterface; kuery: string; logger: Logger; internalESClient: ElasticsearchClient; 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 d7181b3ce49c60..0d5595879899bb 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 @@ -7,10 +7,10 @@ import type { ElasticsearchClient, + HttpServiceSetup, KibanaRequest, - Logger, LoggerFactory, - SavedObjectsClientContract, + SavedObjectsServiceStart, SecurityServiceStart, } from '@kbn/core/server'; import type { ExceptionListClient, ListsServerExtensionRegistrar } from '@kbn/lists-plugin/server'; @@ -24,6 +24,8 @@ import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/aler import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions/types'; import type { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { SavedObjectsClientFactory } from './services/saved_objects'; import type { ResponseActionsClient } from './services'; import { getResponseActionsClient, NormalizedExternalConnectorClient } from './services'; import { @@ -38,7 +40,7 @@ import type { ManifestManager } from './services/artifacts'; import type { ConfigType } from '../config'; import type { IRequestContextFactory } from '../request_context_factory'; import type { LicenseService } from '../../common/license'; -import type { EndpointMetadataService } from './services/metadata'; +import { EndpointMetadataService } from './services/metadata'; import { EndpointAppContentServicesNotSetUpError, EndpointAppContentServicesNotStartedError, @@ -47,6 +49,7 @@ import type { EndpointFleetServicesFactoryInterface, EndpointInternalFleetServicesInterface, } from './services/fleet/endpoint_fleet_services_factory'; +import { EndpointFleetServicesFactory } from './services/fleet/endpoint_fleet_services_factory'; import { registerListsPluginEndpointExtensionPoints } from '../lists_integration'; import type { EndpointAuthz } from '../../common/endpoint/types/authz'; import { calculateEndpointAuthz } from '../../common/endpoint/service/authz'; @@ -54,34 +57,30 @@ import type { FeatureUsageService } from './services/feature_usage/service'; import type { ExperimentalFeatures } from '../../common/experimental_features'; import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service'; import type { ResponseActionAgentType } from '../../common/endpoint/service/response_actions/constants'; + export interface EndpointAppContextServiceSetupContract { securitySolutionRequestContextFactory: IRequestContextFactory; cloud: CloudSetup; loggerFactory: LoggerFactory; + httpServiceSetup: HttpServiceSetup; } export interface EndpointAppContextServiceStartContract { - fleetAuthzService?: FleetStartContract['authz']; - createFleetFilesClient: FleetStartContract['createFilesClient']; - createFleetActionsClient: FleetStartContract['createFleetActionsClient']; - logger: Logger; - endpointMetadataService: EndpointMetadataService; - endpointFleetServicesFactory: EndpointFleetServicesFactoryInterface; - manifestManager?: ManifestManager; + fleetStartServices: FleetStartContract; + manifestManager: ManifestManager; security: SecurityServiceStart; alerting: AlertsPluginStartContract; config: ConfigType; - registerIngestCallback?: FleetStartContract['registerExternalCallback']; registerListsServerExtension?: ListsServerExtensionRegistrar; licenseService: LicenseService; exceptionListsClient: ExceptionListClient | undefined; cases: CasesServerStart | undefined; featureUsageService: FeatureUsageService; experimentalFeatures: ExperimentalFeatures; - messageSigningService: MessageSigningServiceInterface | undefined; + /** An internal ES client */ esClient: ElasticsearchClient; productFeaturesService: ProductFeaturesService; - savedObjectsClient: SavedObjectsClientContract; + savedObjectsServiceStart: SavedObjectsServiceStart; connectorActions: ActionsPluginStartContract; } @@ -93,6 +92,8 @@ export class EndpointAppContextService { private setupDependencies: EndpointAppContextServiceSetupContract | null = null; private startDependencies: EndpointAppContextServiceStartContract | null = null; private fleetServicesFactory: EndpointFleetServicesFactoryInterface | null = null; + private savedObjectsFactoryService: SavedObjectsClientFactory | null = null; + public security: SecurityServiceStart | undefined; public setup(dependencies: EndpointAppContextServiceSetupContract) { @@ -104,92 +105,128 @@ export class EndpointAppContextService { throw new EndpointAppContentServicesNotSetUpError(); } + const savedObjectsFactory = new SavedObjectsClientFactory( + dependencies.savedObjectsServiceStart, + this.setupDependencies.httpServiceSetup + ); + this.startDependencies = dependencies; this.security = dependencies.security; - this.fleetServicesFactory = dependencies.endpointFleetServicesFactory; + this.savedObjectsFactoryService = savedObjectsFactory; + this.fleetServicesFactory = new EndpointFleetServicesFactory( + dependencies.fleetStartServices, + savedObjectsFactory + ); + + this.registerFleetExtensions(); + this.registerListsExtensions(); + } + + public stop() { + this.startDependencies = null; + this.savedObjectsFactoryService = null; + } + + private registerListsExtensions() { + if (this.startDependencies?.registerListsServerExtension) { + registerListsPluginEndpointExtensionPoints( + this.startDependencies?.registerListsServerExtension, + this + ); + } + } + + private registerFleetExtensions() { + if (!this.setupDependencies) { + throw new EndpointAppContentServicesNotSetUpError(); + } + if (!this.startDependencies) { + throw new EndpointAppContentServicesNotStartedError(); + } + + const { + fleetStartServices: { registerExternalCallback: registerFleetCallback }, + manifestManager, + alerting, + licenseService, + exceptionListsClient, + featureUsageService, + esClient, + productFeaturesService, + } = this.startDependencies; + const endpointMetadataService = this.getEndpointMetadataService(); + const soClient = this.savedObjects.createInternalScopedSoClient({ readonly: false }); + const logger = this.createLogger('endpointFleetExtension'); + + registerFleetCallback( + 'agentPolicyCreate', + getAgentPolicyCreateCallback(logger, productFeaturesService) + ); + registerFleetCallback( + 'agentPolicyUpdate', + getAgentPolicyUpdateCallback(logger, productFeaturesService) + ); - if (dependencies.registerIngestCallback && dependencies.manifestManager) { - const { - registerIngestCallback, + registerFleetCallback( + 'packagePolicyCreate', + getPackagePolicyCreateCallback( logger, manifestManager, + this.setupDependencies.securitySolutionRequestContextFactory, alerting, licenseService, exceptionListsClient, + this.setupDependencies.cloud, + productFeaturesService + ) + ); + + registerFleetCallback( + 'packagePolicyPostCreate', + getPackagePolicyPostCreateCallback(logger, exceptionListsClient) + ); + + registerFleetCallback( + 'packagePolicyUpdate', + getPackagePolicyUpdateCallback( + logger, + licenseService, featureUsageService, endpointMetadataService, + this.setupDependencies.cloud, esClient, - productFeaturesService, - savedObjectsClient, - } = dependencies; - - registerIngestCallback( - 'agentPolicyCreate', - getAgentPolicyCreateCallback(logger, productFeaturesService) - ); - registerIngestCallback( - 'agentPolicyUpdate', - getAgentPolicyUpdateCallback(logger, productFeaturesService) - ); - - registerIngestCallback( - 'packagePolicyCreate', - getPackagePolicyCreateCallback( - logger, - manifestManager, - this.setupDependencies.securitySolutionRequestContextFactory, - alerting, - licenseService, - exceptionListsClient, - this.setupDependencies.cloud, - productFeaturesService - ) - ); - - registerIngestCallback( - 'packagePolicyPostCreate', - getPackagePolicyPostCreateCallback(logger, exceptionListsClient) - ); + productFeaturesService + ) + ); - registerIngestCallback( - 'packagePolicyUpdate', - getPackagePolicyUpdateCallback( - logger, - licenseService, - featureUsageService, - endpointMetadataService, - this.setupDependencies.cloud, - esClient, - productFeaturesService - ) - ); + registerFleetCallback( + 'packagePolicyPostDelete', + getPackagePolicyDeleteCallback(exceptionListsClient, soClient) + ); + } - registerIngestCallback( - 'packagePolicyPostDelete', - getPackagePolicyDeleteCallback(exceptionListsClient, savedObjectsClient) - ); + /** + * Property providing access to saved objects client factory + */ + public get savedObjects(): SavedObjectsClientFactory { + if (!this.savedObjectsFactoryService) { + throw new EndpointAppContentServicesNotStartedError(); } - if (this.startDependencies.registerListsServerExtension) { - const { registerListsServerExtension } = this.startDependencies; - - registerListsPluginEndpointExtensionPoints(registerListsServerExtension, this); - } + return this.savedObjectsFactoryService; } - public stop() {} - private getFleetAuthzService(): FleetStartContract['authz'] { - if (!this.startDependencies?.fleetAuthzService) { + if (!this.startDependencies?.fleetStartServices) { throw new EndpointAppContentServicesNotStartedError(); } - return this.startDependencies.fleetAuthzService; + return this.startDependencies.fleetStartServices.authz; } public createLogger(...contextParts: string[]) { if (!this.setupDependencies?.loggerFactory) { - throw new EndpointAppContentServicesNotStartedError(); + throw new EndpointAppContentServicesNotSetUpError(); } return this.setupDependencies.loggerFactory.get(...contextParts); @@ -209,11 +246,17 @@ export class EndpointAppContextService { ); } - public getEndpointMetadataService(): EndpointMetadataService { + public getEndpointMetadataService(spaceId: string = DEFAULT_SPACE_ID): EndpointMetadataService { if (this.startDependencies == null) { throw new EndpointAppContentServicesNotStartedError(); } - return this.startDependencies.endpointMetadataService; + + return new EndpointMetadataService( + this.startDependencies.esClient, + this.savedObjects.createInternalScopedSoClient({ readonly: false }), + this.getInternalFleetServices(), + this.createLogger('endpointMetadata') + ); } public getInternalFleetServices(): EndpointInternalFleetServicesInterface { @@ -266,11 +309,11 @@ export class EndpointAppContextService { } public getMessageSigningService(): MessageSigningServiceInterface { - if (!this.startDependencies?.messageSigningService) { + if (!this.startDependencies?.fleetStartServices.messageSigningService) { throw new EndpointAppContentServicesNotStartedError(); } - return this.startDependencies.messageSigningService; + return this.startDependencies?.fleetStartServices.messageSigningService; } public getInternalResponseActionsClient({ @@ -314,29 +357,29 @@ export class EndpointAppContextService { } public async getFleetToHostFilesClient() { - if (!this.startDependencies?.createFleetFilesClient) { + if (!this.startDependencies?.fleetStartServices) { throw new EndpointAppContentServicesNotStartedError(); } - return this.startDependencies.createFleetFilesClient.toHost( + return this.startDependencies.fleetStartServices.createFilesClient.toHost( 'endpoint', this.startDependencies.config.maxUploadResponseActionFileBytes ); } public async getFleetFromHostFilesClient(): Promise { - if (!this.startDependencies?.createFleetFilesClient) { + if (!this.startDependencies?.fleetStartServices) { throw new EndpointAppContentServicesNotStartedError(); } - return this.startDependencies.createFleetFilesClient.fromHost('endpoint'); + return this.startDependencies.fleetStartServices.createFilesClient.fromHost('endpoint'); } public async getFleetActionsClient(): Promise { - if (!this.startDependencies?.createFleetActionsClient) { + if (!this.startDependencies?.fleetStartServices) { throw new EndpointAppContentServicesNotStartedError(); } - return this.startDependencies.createFleetActionsClient('endpoint'); + return this.startDependencies.fleetStartServices.createFleetActionsClient('endpoint'); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.test.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.test.ts index 6279870f927522..b031a7882bf3ac 100644 --- a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { createMockEndpointAppContextServiceStartContract } from '../mocks'; import type { Logger } from '@kbn/logging'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; @@ -13,6 +12,9 @@ import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service'; import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks'; import { turnOffAgentPolicyFeatures } from './turn_off_agent_policy_features'; +import { createEndpointFleetServicesFactoryMock } from '../services/fleet/endpoint_fleet_services_factory.mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { allowedExperimentalValues } from '../../../common'; describe('Turn Off Agent Policy Features Migration', () => { let fleetServices: EndpointInternalFleetServicesInterface; @@ -23,12 +25,16 @@ describe('Turn Off Agent Policy Features Migration', () => { turnOffAgentPolicyFeatures(fleetServices, productFeatureService, logger); beforeEach(() => { - const endpointContextStartContract = createMockEndpointAppContextServiceStartContract(); + const mockedFleetServices = createEndpointFleetServicesFactoryMock(); - ({ logger } = endpointContextStartContract); - - productFeatureService = endpointContextStartContract.productFeaturesService; - fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); + fleetServices = mockedFleetServices.service.asInternalUser(); + logger = loggingSystemMock.createLogger(); + productFeatureService = createProductFeaturesServiceMock( + undefined, + allowedExperimentalValues, + undefined, + logger + ); }); describe('and `agentTamperProtection` is enabled', () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.ts index cfb520719172d8..c0543236189911 100644 --- a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.ts +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_agent_policy_features.ts @@ -29,7 +29,8 @@ export const turnOffAgentPolicyFeatures = async ( `App feature [${ProductFeatureSecurityKey.endpointAgentTamperProtection}] is disabled. Checking fleet agent policies for compliance` ); - const { agentPolicy: agentPolicyService, internalSoClient } = fleetServices; + const { agentPolicy: agentPolicyService, savedObjects } = fleetServices; + const internalSoClient = savedObjects.createInternalScopedSoClient({ readonly: false }); const { updatedPolicies, failedPolicies } = await agentPolicyService.turnOffAgentTamperProtections(internalSoClient); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts index a87d16b3b7f5b2..66a20569bdee86 100644 --- a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts @@ -19,6 +19,8 @@ import type { PromiseResolvedValue } from '../../../common/endpoint/types/utilit import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service'; import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { createEndpointFleetServicesFactoryMock } from '../services/fleet/endpoint_fleet_services_factory.mocks'; describe('Turn Off Policy Protections Migration', () => { let esClient: ElasticsearchClient; @@ -58,10 +60,11 @@ describe('Turn Off Policy Protections Migration', () => { beforeEach(() => { const endpointContextStartContract = createMockEndpointAppContextServiceStartContract(); - ({ esClient, logger } = endpointContextStartContract); - + logger = loggingSystemMock.createLogger(); + ({ esClient } = endpointContextStartContract); productFeatureService = endpointContextStartContract.productFeaturesService; - fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); + + fleetServices = createEndpointFleetServicesFactoryMock().service.asInternalUser(); }); describe('and both `endpointPolicyProtections` and `endpointProtectionUpdates` is enabled', () => { @@ -139,7 +142,7 @@ describe('Turn Off Policy Protections Migration', () => { expect(fleetServices.packagePolicy.list as jest.Mock).toHaveBeenCalledTimes(2); expect(fleetServices.packagePolicy.bulkUpdate as jest.Mock).toHaveBeenCalledWith( - fleetServices.internalSoClient, + fleetServices.savedObjects.createInternalScopedSoClient({ readonly: false }), esClient, [ expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![0].id }), @@ -210,7 +213,7 @@ describe('Turn Off Policy Protections Migration', () => { expect(fleetServices.packagePolicy.list as jest.Mock).toHaveBeenCalledTimes(2); expect(fleetServices.packagePolicy.bulkUpdate as jest.Mock).toHaveBeenCalledWith( - fleetServices.internalSoClient, + fleetServices.savedObjects.createInternalScopedSoClient({ readonly: false }), esClient, [ expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![0].id }), @@ -317,7 +320,7 @@ describe('Turn Off Policy Protections Migration', () => { expect(fleetServices.packagePolicy.list as jest.Mock).toHaveBeenCalledTimes(2); expect(fleetServices.packagePolicy.bulkUpdate as jest.Mock).toHaveBeenCalledWith( - fleetServices.internalSoClient, + fleetServices.savedObjects.createInternalUnscopedSoClient(), esClient, [ expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![0].id }), diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts index fa93e8c0b62f69..43e30b25b4168a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts @@ -62,7 +62,8 @@ export const turnOffPolicyProtectionsIfNotSupported = async ( ); } - const { packagePolicy, internalSoClient, endpointPolicyKuery } = fleetServices; + const { packagePolicy, savedObjects, endpointPolicyKuery } = fleetServices; + const internalSoClient = savedObjects.createInternalScopedSoClient({ readonly: false }); const updates: UpdatePackagePolicy[] = []; const messages: string[] = []; const perPage = 1000; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts index 141a5ebb440f6d..f60192a32796b4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts @@ -16,6 +16,7 @@ import { savedObjectsClientMock, savedObjectsServiceMock, securityServiceMock, + coreMock, } from '@kbn/core/server/mocks'; import type { IRouter, @@ -24,23 +25,20 @@ import type { RouteConfig, RouteMethod, SavedObjectsClientContract, + SecurityServiceStart, } from '@kbn/core/server'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; -import type { FleetStartContract } from '@kbn/fleet-plugin/server'; import { createFleetActionsClientMock, createFleetFromHostFilesClientMock, + createFleetStartContractMock, createFleetToHostFilesClientMock, createMessageSigningServiceMock, - createMockAgentPolicyService, - createMockAgentService, - createMockPackageService, createPackagePolicyServiceMock, } from '@kbn/fleet-plugin/server/mocks'; -import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; import type { RequestFixtureOptions, RouterMock } from '@kbn/core-http-router-server-mocks'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; @@ -48,8 +46,12 @@ import { casesPluginMock } from '@kbn/cases-plugin/server/mocks'; import { createCasesClientMock } from '@kbn/cases-plugin/server/client/mocks'; import type { AddVersionOpts, VersionedRouteConfig } from '@kbn/core-http-server'; import { unsecuredActionsClientMock } from '@kbn/actions-plugin/server/unsecured_actions_client/unsecured_actions_client.mock'; -import type { PluginStartContract } from '@kbn/actions-plugin/server'; +import type { PluginStartContract as ActionPluginStartContract } from '@kbn/actions-plugin/server'; import type { Mutable } from 'utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { EndpointMetadataService } from '../services/metadata'; +import { createEndpointFleetServicesFactoryMock } from '../services/fleet/endpoint_fleet_services_factory.mocks'; +import type { ProductFeaturesService } from '../../lib/product_features_service'; import { responseActionsClientMock } from '../services/actions/clients/mocks'; import { getEndpointAuthzInitialStateMock } from '../../../common/endpoint/service/authz/mocks'; import { createMockConfig, requestContextMock } from '../../lib/detection_engine/routes/__mocks__'; @@ -66,15 +68,13 @@ import { parseExperimentalConfigValue, } from '../../../common/experimental_features'; import { requestContextFactoryMock } from '../../request_context_factory.mock'; -import { EndpointMetadataService } from '../services/metadata'; import type { SecuritySolutionRequestHandlerContextMock } from '../../lib/detection_engine/routes/__mocks__/request_context'; import { createMockClients } from '../../lib/detection_engine/routes/__mocks__/request_context'; -import { createEndpointMetadataServiceTestContextMock } from '../services/metadata/mocks'; import type { EndpointAuthz } from '../../../common/endpoint/types/authz'; -import { EndpointFleetServicesFactory } from '../services/fleet'; import { createLicenseServiceMock } from '../../../common/license/mocks'; import { createFeatureUsageServiceMock } from '../services/feature_usage/mocks'; import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks'; + /** * Creates a mocked EndpointAppContext. */ @@ -98,7 +98,15 @@ export const createMockEndpointAppContext = ( export const createMockEndpointAppContextService = ( mockManifestManager?: ManifestManager ): jest.Mocked => { - const mockEndpointMetadataContext = createEndpointMetadataServiceTestContextMock(); + const { esClient, fleetStartServices } = createMockEndpointAppContextServiceStartContract(); + const fleetServices = createEndpointFleetServicesFactoryMock({ + fleetDependencies: fleetStartServices, + }).service.asInternalUser(); + const endpointMetadataService = new EndpointMetadataService( + esClient, + savedObjectsClientMock.create(), + fleetServices + ); const casesClientMock = createCasesClientMock(); const fleetFromHostFilesClientMock = createFleetFromHostFilesClientMock(); const fleetToHostFilesClientMock = createFleetToHostFilesClientMock(); @@ -116,8 +124,8 @@ export const createMockEndpointAppContextService = ( }, createLogger: jest.fn((...parts) => loggerFactory.get(...parts)), getManifestManager: jest.fn().mockReturnValue(mockManifestManager ?? jest.fn()), - getEndpointMetadataService: jest.fn(() => mockEndpointMetadataContext.endpointMetadataService), - getInternalFleetServices: jest.fn(() => mockEndpointMetadataContext.fleetServices), + getEndpointMetadataService: jest.fn(() => endpointMetadataService), + getInternalFleetServices: jest.fn(() => fleetServices), getEndpointAuthz: jest.fn(async (_) => getEndpointAuthzInitialStateMock()), getCasesClient: jest.fn().mockReturnValue(casesClientMock), getFleetFromHostFilesClient: jest.fn(async () => fleetFromHostFilesClientMock), @@ -143,6 +151,7 @@ export const createMockEndpointAppContextServiceSetupContract = securitySolutionRequestContextFactory: requestContextFactoryMock.create(), cloud: cloudMock.createSetup(), loggerFactory: loggingSystemMock.create(), + httpServiceSetup: coreMock.createSetup().http, }; }; @@ -150,38 +159,13 @@ export const createMockEndpointAppContextServiceSetupContract = * Creates a mocked input contract for the `EndpointAppContextService#start()` method */ export const createMockEndpointAppContextServiceStartContract = - (): jest.Mocked => { + (): DeeplyMockedKeys => { const config = createMockConfig(); const logger = loggingSystemMock.create().get('mock_endpoint_app_context'); - const savedObjectsStart = savedObjectsServiceMock.createStartContract(); - const security = securityServiceMock.createStart(); - const agentService = createMockAgentService(); - const agentPolicyService = createMockAgentPolicyService(); + const security = + securityServiceMock.createStart() as unknown as DeeplyMockedKeys; const packagePolicyService = createPackagePolicyServiceMock(); - const packageService = createMockPackageService(); - const endpointMetadataService = new EndpointMetadataService( - savedObjectsStart, - agentPolicyService, - packagePolicyService, - logger - ); - const endpointFleetServicesFactory = new EndpointFleetServicesFactory( - { - packageService, - packagePolicyService, - agentPolicyService, - agentService, - }, - savedObjectsStart - ); - const experimentalFeatures = config.experimentalFeatures; - const productFeaturesService = createProductFeaturesServiceMock( - undefined, - experimentalFeatures, - undefined, - logger - ); packagePolicyService.list.mockImplementation(async (_, options) => { return { @@ -197,47 +181,32 @@ export const createMockEndpointAppContextServiceStartContract = securityMock.createMockAuthenticatedUser({ roles: ['superuser'] }) ); - const casesMock = casesPluginMock.createStartContract(); - const fleetActionsClientMock = createFleetActionsClientMock(); - - return { - endpointMetadataService, - endpointFleetServicesFactory, - logger, - fleetAuthzService: createFleetAuthzServiceMock(), - createFleetFilesClient: { - fromHost: jest.fn((..._) => createFleetFromHostFilesClientMock()), - toHost: jest.fn((..._) => createFleetToHostFilesClientMock()), - }, - manifestManager: getManifestManagerMock(), + const startContract: DeeplyMockedKeys = { security, - alerting: alertsMock.createStart(), config, + productFeaturesService: createProductFeaturesServiceMock( + undefined, + config.experimentalFeatures, + undefined, + logger + ) as DeeplyMockedKeys, + experimentalFeatures: config.experimentalFeatures, + fleetStartServices: createFleetStartContractMock(), + cases: casesPluginMock.createStartContract(), + manifestManager: getManifestManagerMock() as DeeplyMockedKeys, + alerting: alertsMock.createStart(), licenseService: createLicenseServiceMock(), - registerIngestCallback: jest.fn< - ReturnType, - Parameters - >(), exceptionListsClient: listMock.getExceptionListClient(), - cases: casesMock, featureUsageService: createFeatureUsageServiceMock(), - experimentalFeatures, - messageSigningService: createMessageSigningServiceMock(), - createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), esClient: elasticsearchClientMock.createElasticsearchClient(), - productFeaturesService, - savedObjectsClient: savedObjectsClientMock.create(), + savedObjectsServiceStart: savedObjectsServiceMock.createStartContract(), connectorActions: { getUnsecuredActionsClient: jest.fn().mockReturnValue(unsecuredActionsClientMock.create()), - } as unknown as jest.Mocked, + } as unknown as jest.Mocked, }; - }; -export const createFleetAuthzServiceMock = (): jest.Mocked => { - return { - fromRequest: jest.fn(async (_) => createFleetAuthzMock()), + return startContract; }; -}; export function createRouteHandlerContext( dataClient: ScopedClusterClientMock, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 669d3770be37dc..66b804e07eb104 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -142,7 +142,9 @@ describe('Response actions', () => { const routerMock = httpServiceMock.createRouter(); mockResponse = httpServerMock.createResponseFactory(); const startContract = createMockEndpointAppContextServiceStartContract(); - (startContract.messageSigningService?.sign as jest.Mock).mockImplementation(() => { + ( + startContract.fleetStartServices.messageSigningService?.sign as jest.Mock + ).mockImplementation(() => { return { data: 'thisisthedata', signature: 'thisisasignature', @@ -163,6 +165,7 @@ describe('Response actions', () => { endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, + esClient: mockScopedClient.asInternalUser, licenseService, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 6641a148d49a1e..90eb56fbc83f2b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -46,17 +46,9 @@ export function getMetadataListRequestHandler( > { return async (context, request, response) => { const endpointMetadataService = endpointAppContext.service.getEndpointMetadataService(); - const fleetServices = endpointAppContext.service.getInternalFleetServices(); - const esClient = (await context.core).elasticsearch.client.asInternalUser; - const soClient = (await context.core).savedObjects.client; try { - const { data, total } = await endpointMetadataService.getHostMetadataList( - esClient, - soClient, - fleetServices, - request.query - ); + const { data, total } = await endpointMetadataService.getHostMetadataList(request.query); const body: MetadataListResponse = { data, @@ -88,13 +80,8 @@ export const getMetadataRequestHandler = function ( const endpointMetadataService = endpointAppContext.service.getEndpointMetadataService(); try { - const esClient = (await context.core).elasticsearch.client; return response.ok({ - body: await endpointMetadataService.getEnrichedHostMetadata( - esClient.asInternalUser, - endpointAppContext.service.getInternalFleetServices(), - request.params.id - ), + body: await endpointMetadataService.getEnrichedHostMetadata(request.params.id), }); } catch (error) { return errorHandler(logger, response, error); 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 f1061db4e8c597..63d3c466dd2b6c 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 @@ -94,8 +94,7 @@ describe('test endpoint routes', () => { startContract = createMockEndpointAppContextServiceStartContract(); ( - startContract.endpointFleetServicesFactory.asInternalUser() - .packagePolicy as jest.Mocked + startContract.fleetStartServices.packagePolicyService as jest.Mocked ).list.mockImplementation(() => { return Promise.resolve({ items: [], @@ -107,11 +106,14 @@ describe('test endpoint routes', () => { endpointAppContextService = new EndpointAppContextService(); endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); - endpointAppContextService.start({ ...startContract }); - mockAgentClient = startContract.endpointFleetServicesFactory.asInternalUser() - .agent as jest.Mocked; - mockAgentPolicyService = startContract.endpointFleetServicesFactory.asInternalUser() - .agentPolicy as jest.Mocked; + endpointAppContextService.start({ + ...startContract, + esClient: mockScopedClient.asInternalUser, + }); + mockAgentClient = startContract.fleetStartServices.agentService + .asInternalUser as jest.Mocked; + mockAgentPolicyService = startContract.fleetStartServices + .agentPolicyService as jest.Mocked; registerEndpointRoutes(routerMock, { ...createMockEndpointAppContext(), diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts index df2b3c323ee08f..0aeedb51bc512a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts @@ -73,7 +73,7 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { const uniqueIds = [...new Set(ids)]; const foundEndpointHosts = await this.options.endpointService .getEndpointMetadataService() - .getMetadataForEndpoints(this.options.esClient, uniqueIds); + .getMetadataForEndpoints(uniqueIds); const validIds = foundEndpointHosts.map((endpoint: HostMetadata) => endpoint.elastic.agent.id); const invalidIds = ids.filter((id) => !validIds.includes(id)); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/mocks.ts index a721bda2f38ab5..69901033eaafd1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/mocks.ts @@ -106,7 +106,10 @@ const createConstructorOptionsMock = (): Required { acc[endpointMetadata.elastic.agent.id] = new Date(endpointMetadata.event.created); return acc; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils/utils.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils/utils.ts index 9c9f8e5b62cac4..42805e49f894fa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils/utils.ts @@ -550,9 +550,7 @@ export const getAgentHostNamesWithIds = async ({ metadataService: EndpointMetadataService; }): Promise<{ [id: string]: string }> => { // get host metadata docs with queried agents - const metaDataDocs = await metadataService.findHostMetadataForFleetAgents(esClient, [ - ...new Set(agentIds), - ]); + const metaDataDocs = await metadataService.findHostMetadataForFleetAgents([...new Set(agentIds)]); // agent ids and names from metadata // map this into an object as {id1: name1, id2: name2} etc const agentsMetadataInfo = agentIds.reduce<{ [id: string]: string }>((acc, id) => { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts index c7b060833a64bf..ed8e4f45a13676 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/agent/clients/endpoint/endpoint_agent_status_client.ts @@ -18,21 +18,15 @@ export class EndpointAgentStatusClient extends AgentStatusClient { async getAgentStatuses(agentIds: string[]): Promise { const metadataService = this.options.endpointService.getEndpointMetadataService(); const esClient = this.options.esClient; - const soClient = this.options.soClient; try { const agentIdsKql = agentIds.map((agentId) => `agent.id: ${agentId}`).join(' or '); const [{ data: hostInfoForAgents }, allPendingActions] = await Promise.all([ - metadataService.getHostMetadataList( - esClient, - soClient, - this.options.endpointService.getInternalFleetServices(), - { - page: 0, - pageSize: 1000, - kuery: agentIdsKql, - } - ), + metadataService.getHostMetadataList({ + page: 0, + pageSize: 1000, + kuery: agentIdsKql, + }), getPendingActionsSummary(esClient, metadataService, this.log, agentIds), ]).catch(catchAndWrapError); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.mocks.ts new file mode 100644 index 00000000000000..1e37993c955010 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.mocks.ts @@ -0,0 +1,46 @@ +/* + * 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 { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import type { FleetStartContract } from '@kbn/fleet-plugin/server'; +import { createFleetStartContractMock } from '@kbn/fleet-plugin/server/mocks'; +import type { SavedObjectsClientFactory } from '../saved_objects'; +import type { EndpointFleetServicesFactoryInterface } from './endpoint_fleet_services_factory'; +import { EndpointFleetServicesFactory } from './endpoint_fleet_services_factory'; +import { createSavedObjectsClientFactoryMock } from '../saved_objects/saved_objects_client_factory.mocks'; + +interface EndpointFleetServicesFactoryInterfaceMocked + extends EndpointFleetServicesFactoryInterface { + asInternalUser: () => DeeplyMockedKeys< + ReturnType + >; +} + +interface CreateEndpointFleetServicesFactoryMockOptions { + fleetDependencies: DeeplyMockedKeys; + savedObjects: SavedObjectsClientFactory; +} + +export const createEndpointFleetServicesFactoryMock = ( + dependencies: Partial = {} +): { + service: EndpointFleetServicesFactoryInterfaceMocked; + dependencies: CreateEndpointFleetServicesFactoryMockOptions; +} => { + const { + fleetDependencies = createFleetStartContractMock(), + savedObjects = createSavedObjectsClientFactoryMock().service, + } = dependencies; + + return { + service: new EndpointFleetServicesFactory( + fleetDependencies, + savedObjects + ) as unknown as EndpointFleetServicesFactoryInterfaceMocked, + dependencies: { fleetDependencies, savedObjects }, + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts index 1f3df9d6a67d3a..27df7645b7fc26 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { SavedObjectsClientContract, SavedObjectsServiceStart } from '@kbn/core/server'; import type { AgentClient, AgentPolicyServiceInterface, @@ -14,20 +13,35 @@ import type { PackageClient, } from '@kbn/fleet-plugin/server'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; -import { createInternalSoClient } from '../../utils/create_internal_so_client'; -import { createInternalReadonlySoClient } from '../../utils/create_internal_readonly_so_client'; +import type { SavedObjectsClientFactory } from '../saved_objects'; + +/** + * The set of Fleet services used by Endpoint + */ +export interface EndpointFleetServicesInterface { + agent: AgentClient; + agentPolicy: AgentPolicyServiceInterface; + packages: PackageClient; + packagePolicy: PackagePolicyClient; + /** The `kuery` that can be used to filter for Endpoint integration policies */ + endpointPolicyKuery: string; +} + +export interface EndpointInternalFleetServicesInterface extends EndpointFleetServicesInterface { + savedObjects: SavedObjectsClientFactory; +} export interface EndpointFleetServicesFactoryInterface { asInternalUser(): EndpointInternalFleetServicesInterface; } +/** + * Provides centralized way to get all services for Fleet and access internal saved object clients + */ export class EndpointFleetServicesFactory implements EndpointFleetServicesFactoryInterface { constructor( - private readonly fleetDependencies: Pick< - FleetStartContract, - 'agentService' | 'packageService' | 'packagePolicyService' | 'agentPolicyService' - >, - private savedObjectsStart: SavedObjectsServiceStart + private readonly fleetDependencies: FleetStartContract, + private readonly savedObjects: SavedObjectsClientFactory ) {} asInternalUser(): EndpointInternalFleetServicesInterface { @@ -41,35 +55,13 @@ export class EndpointFleetServicesFactory implements EndpointFleetServicesFactor return { agent: agentService.asInternalUser, agentPolicy, + packages: packageService.asInternalUser, packagePolicy, endpointPolicyKuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "endpoint"`, - internalReadonlySoClient: createInternalReadonlySoClient(this.savedObjectsStart), - internalSoClient: createInternalSoClient(this.savedObjectsStart), + savedObjects: this.savedObjects, }; } } - -/** - * The set of Fleet services used by Endpoint - */ -export interface EndpointFleetServicesInterface { - agent: AgentClient; - agentPolicy: AgentPolicyServiceInterface; - packages: PackageClient; - packagePolicy: PackagePolicyClient; - /** The `kuery` that can be used to filter for Endpoint integration policies */ - endpointPolicyKuery: string; -} - -export interface EndpointInternalFleetServicesInterface extends EndpointFleetServicesInterface { - /** - * An internal SO client (readonly) that can be used with the Fleet services that require it - */ - internalReadonlySoClient: SavedObjectsClientContract; - - /** Internal SO client. USE ONLY WHEN ABSOLUTELY NEEDED. Else, use the `internalReadonlySoClient` */ - internalSoClient: SavedObjectsClientContract; -} diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts index 750e746761e7aa..2fe173ff55eb5c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts @@ -7,7 +7,7 @@ import { uniq } from 'lodash'; import type { EndpointMetadataServiceTestContextMock } from './mocks'; import { createEndpointMetadataServiceTestContextMock } from './mocks'; -import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { legacyMetadataSearchResponseMock, @@ -37,7 +37,7 @@ describe('EndpointMetadataService', () => { endpointDocGenerator = new EndpointDocGenerator('seed'); testMockedContext = createEndpointMetadataServiceTestContextMock(); metadataService = testMockedContext.endpointMetadataService; - esClient = elasticsearchServiceMock.createScopedClusterClient().asInternalUser; + esClient = testMockedContext.esClient; soClient = savedObjectsClientMock.create(); soClient.find = jest.fn().mockResolvedValue({ saved_objects: [] }); fleetAppContextService.start( @@ -58,7 +58,7 @@ describe('EndpointMetadataService', () => { }); it('should call elasticsearch with proper filter', async () => { - await metadataService.findHostMetadataForFleetAgents(esClient, fleetAgentIds); + await metadataService.findHostMetadataForFleetAgents(fleetAgentIds); expect(esClient.search).toHaveBeenCalledWith( { ...getESQueryHostMetadataByFleetAgentIds(fleetAgentIds), size: fleetAgentIds.length }, { ignore: [404] } @@ -67,16 +67,13 @@ describe('EndpointMetadataService', () => { it('should throw a wrapped elasticsearch Error when one occurs', async () => { esClient.search.mockRejectedValue(new Error('foo bar')); - await expect( - metadataService.findHostMetadataForFleetAgents(esClient, fleetAgentIds) - ).rejects.toThrow(EndpointError); + await expect(metadataService.findHostMetadataForFleetAgents(fleetAgentIds)).rejects.toThrow( + EndpointError + ); }); it('should return an array of Host Metadata documents', async () => { - const response = await metadataService.findHostMetadataForFleetAgents( - esClient, - fleetAgentIds - ); + const response = await metadataService.findHostMetadataForFleetAgents(fleetAgentIds); expect(response).toEqual([endpointMetadataDoc]); }); }); @@ -86,22 +83,16 @@ describe('EndpointMetadataService', () => { beforeEach(() => { agentPolicyServiceMock = testMockedContext.agentPolicyService; - esClient = elasticsearchServiceMock.createScopedClusterClient().asInternalUser; }); it('should throw wrapped error if es error', async () => { esClient.search.mockRejectedValue({}); - const metadataListResponse = metadataService.getHostMetadataList( - esClient, - soClient, - testMockedContext.fleetServices, - { - page: 0, - pageSize: 10, - kuery: '', - hostStatuses: [], - } - ); + const metadataListResponse = metadataService.getHostMetadataList({ + page: 0, + pageSize: 10, + kuery: '', + hostStatuses: [], + }); await expect(metadataListResponse).rejects.toThrow(EndpointError); }); @@ -109,17 +100,12 @@ describe('EndpointMetadataService', () => { esClient.search.mockRejectedValue({ meta: { body: { error: { type: 'index_not_found_exception' } } }, }); - const metadataListResponse = await metadataService.getHostMetadataList( - esClient, - soClient, - testMockedContext.fleetServices, - { - page: 0, - pageSize: 10, - kuery: '', - hostStatuses: [], - } - ); + const metadataListResponse = await metadataService.getHostMetadataList({ + page: 0, + pageSize: 10, + kuery: '', + hostStatuses: [], + }); expect(metadataListResponse).toEqual({ data: [], @@ -156,30 +142,23 @@ describe('EndpointMetadataService', () => { const mockDoc = unitedMetadataSearchResponseMock(endpointMetadataDoc, mockAgent); esClient.search.mockResponse(mockDoc); agentPolicyServiceMock.getByIds.mockResolvedValue(agentPolicies); - testMockedContext.packagePolicyService.list.mockImplementation( - async (_, { page, perPage }) => { - const response = { - items: packagePolicies, - page: page ?? 1, - total: packagePolicies.length, - perPage: packagePolicies.length, - }; - - if ((page ?? 1) > 1) { - response.items = []; - } - - return response; + testMockedContext.packagePolicyService.list.mockImplementation(async (_, { page }) => { + const response = { + items: packagePolicies, + page: page ?? 1, + total: packagePolicies.length, + perPage: packagePolicies.length, + }; + + if ((page ?? 1) > 1) { + response.items = []; } - ); + + return response; + }); const queryOptions = { page: 1, pageSize: 10, kuery: '', hostStatuses: [] }; - const metadataListResponse = await metadataService.getHostMetadataList( - esClient, - soClient, - testMockedContext.fleetServices, - queryOptions - ); + const metadataListResponse = await metadataService.getHostMetadataList(queryOptions); const unitedIndexQuery = await buildUnitedIndexQuery( soClient, queryOptions, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts index f5735f653efd11..3f3d756c70aab3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts @@ -5,16 +5,10 @@ * 2.0. */ import { uniq } from 'lodash'; -import type { - ElasticsearchClient, - Logger, - SavedObjectsClientContract, - SavedObjectsServiceStart, -} from '@kbn/core/server'; +import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from '@kbn/core/server'; import type { SearchResponse, SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import type { Agent, AgentPolicy, PackagePolicy } from '@kbn/fleet-plugin/common'; -import type { AgentPolicyServiceInterface, PackagePolicyClient } from '@kbn/fleet-plugin/server'; import { AgentNotFoundError } from '@kbn/fleet-plugin/server'; import type { HostInfo, @@ -48,7 +42,6 @@ import { fleetAgentStatusToEndpointHostStatus, wrapErrorIfNeeded, } from '../../utils'; -import { createInternalReadonlySoClient } from '../../utils/create_internal_readonly_so_client'; import { getAllEndpointPackagePolicies } from '../../routes/metadata/support/endpoint_package_policies'; import type { GetMetadataListRequestQuery } from '../../../../common/api/endpoint'; import { EndpointError } from '../../../../common/endpoint/errors'; @@ -65,52 +58,25 @@ const isAgentPolicyWithPackagePolicies = ( }; export class EndpointMetadataService { - /** - * For internal use only by the `this.DANGEROUS_INTERNAL_SO_CLIENT` - * @deprecated - */ - private __DANGEROUS_INTERNAL_SO_CLIENT: SavedObjectsClientContract | undefined; - constructor( - private savedObjectsStart: SavedObjectsServiceStart, - private readonly agentPolicyService: AgentPolicyServiceInterface, - private readonly packagePolicyService: PackagePolicyClient, + private readonly esClient: ElasticsearchClient, + private readonly soClient: SavedObjectsClientContract, + private readonly fleetServices: EndpointFleetServicesInterface, private readonly logger?: Logger ) {} - /** - * An INTERNAL Saved Object client that is effectively the system user and has all privileges and permissions and - * can access any saved object. Used primarly to retrieve fleet data for endpoint enrichment (so that users are - * not required to have superuser role) - * - * **IMPORTANT: SHOULD BE USED ONLY FOR READ-ONLY ACCESS AND WITH DISCRETION** - * - * @private - */ - private get DANGEROUS_INTERNAL_SO_CLIENT() { - // The INTERNAL SO client must be created during the first time its used. This is because creating it during - // instance initialization (in `constructor(){}`) causes the SO Client to be invalid (perhaps because this - // instantiation is happening during the plugin's the start phase) - if (!this.__DANGEROUS_INTERNAL_SO_CLIENT) { - this.__DANGEROUS_INTERNAL_SO_CLIENT = createInternalReadonlySoClient(this.savedObjectsStart); - } - - return this.__DANGEROUS_INTERNAL_SO_CLIENT; - } - /** * Retrieve a single endpoint host metadata. Note that the return endpoint document, if found, * could be associated with a Fleet Agent that is no longer active. If wanting to ensure the * endpoint is associated with an active Fleet Agent, then use `getEnrichedHostMetadata()` instead * - * @param esClient Elasticsearch Client (usually scoped to the user's context) * @param endpointId the endpoint id (from `agent.id`) * * @throws */ - async getHostMetadata(esClient: ElasticsearchClient, endpointId: string): Promise { + async getHostMetadata(endpointId: string): Promise { const query = getESQueryHostMetadataByID(endpointId); - const queryResult = await esClient.search(query).catch(catchAndWrapError); + const queryResult = await this.esClient.search(query).catch(catchAndWrapError); const endpointMetadata = queryResponseToHostResult(queryResult).result; if (endpointMetadata) { @@ -122,19 +88,15 @@ export class EndpointMetadataService { /** * Find a list of Endpoint Host Metadata document associated with a given list of Fleet Agent Ids - * @param esClient * @param fleetAgentIds */ - async findHostMetadataForFleetAgents( - esClient: ElasticsearchClient, - fleetAgentIds: string[] - ): Promise { + async findHostMetadataForFleetAgents(fleetAgentIds: string[]): Promise { const query = getESQueryHostMetadataByFleetAgentIds(fleetAgentIds); // @ts-expect-error `size` not defined as top level property when using `typesWithBodyKey` query.size = fleetAgentIds.length; - const searchResult = await esClient + const searchResult = await this.esClient .search(query, { ignore: [404] }) .catch(catchAndWrapError); @@ -144,18 +106,12 @@ 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 { - const endpointMetadata = await this.getHostMetadata(esClient, endpointId); + async getEnrichedHostMetadata(endpointId: string): Promise { + const endpointMetadata = await this.getHostMetadata(endpointId); let fleetAgentId = endpointMetadata.elastic.agent.id; let fleetAgent: Agent | undefined; @@ -167,7 +123,7 @@ export class EndpointMetadataService { this.logger?.warn(`Missing elastic agent id, using host id instead ${fleetAgentId}`); } - fleetAgent = await this.getFleetAgent(fleetServices.agent, fleetAgentId); + fleetAgent = await this.getFleetAgent(fleetAgentId); } catch (error) { if (error instanceof FleetAgentNotFoundError) { this.logger?.debug(`agent with id ${fleetAgentId} not found`); @@ -183,12 +139,12 @@ export class EndpointMetadataService { ); } - return this.enrichHostMetadata(fleetServices, endpointMetadata, fleetAgent); + return this.enrichHostMetadata(endpointMetadata, fleetAgent); } /** * Enriches a host metadata document with data from fleet - * @param fleetServices + * * @param endpointMetadata * @param _fleetAgent * @param _fleetAgentPolicy @@ -197,7 +153,6 @@ export class EndpointMetadataService { */ // eslint-disable-next-line complexity private async enrichHostMetadata( - fleetServices: EndpointFleetServicesInterface, endpointMetadata: HostMetadata, /** * If undefined, it will be retrieved from Fleet using the ID in the endpointMetadata. @@ -233,7 +188,7 @@ export class EndpointMetadataService { ); } - fleetAgent = await this.getFleetAgent(fleetServices.agent, fleetAgentId); + fleetAgent = await this.getFleetAgent(fleetAgentId); } catch (error) { if (error instanceof FleetAgentNotFoundError) { this.logger?.warn(`Agent with id ${fleetAgentId} not found`); @@ -302,15 +257,11 @@ export class EndpointMetadataService { /** * Retrieve a single Fleet Agent data * - * @param fleetAgentService * @param agentId The elastic agent id (`from `elastic.agent.id`) */ - async getFleetAgent( - fleetAgentService: EndpointFleetServicesInterface['agent'], - agentId: string - ): Promise { + async getFleetAgent(agentId: string): Promise { try { - return await fleetAgentService.getAgent(agentId); + return await this.fleetServices.agent.getAgent(agentId); } catch (error) { if (error instanceof AgentNotFoundError) { throw new FleetAgentNotFoundError(`agent with id ${agentId} not found`, error); @@ -328,8 +279,8 @@ export class EndpointMetadataService { * @throws */ async getFleetAgentPolicy(agentPolicyId: string): Promise { - const agentPolicy = await this.agentPolicyService - .get(this.DANGEROUS_INTERNAL_SO_CLIENT, agentPolicyId, true) + const agentPolicy = await this.fleetServices.agentPolicy + .get(this.soClient, agentPolicyId, true) .catch(catchAndWrapError); if (agentPolicy) { @@ -347,8 +298,8 @@ export class EndpointMetadataService { * @throws */ async getFleetEndpointPackagePolicy(endpointPolicyId: string): Promise { - const endpointPackagePolicy = await this.packagePolicyService - .get(this.DANGEROUS_INTERNAL_SO_CLIENT, endpointPolicyId) + const endpointPackagePolicy = await this.fleetServices.packagePolicy + .get(this.soClient, endpointPolicyId) .catch(catchAndWrapError); if (!endpointPackagePolicy) { @@ -363,27 +314,25 @@ export class EndpointMetadataService { /** * Retrieve list of host metadata. Only supports new united index. * - * @param esClient * @param queryOptions - * @param soClient - * @param fleetServices * * @throws */ async getHostMetadataList( - esClient: ElasticsearchClient, - soClient: SavedObjectsClientContract, - fleetServices: EndpointFleetServicesInterface, queryOptions: GetMetadataListRequestQuery ): Promise> { const endpointPolicies = await this.getAllEndpointPackagePolicies(); const endpointPolicyIds = uniq(endpointPolicies.flatMap((policy) => policy.policy_ids)); - const unitedIndexQuery = await buildUnitedIndexQuery(soClient, queryOptions, endpointPolicyIds); + const unitedIndexQuery = await buildUnitedIndexQuery( + this.soClient, + queryOptions, + endpointPolicyIds + ); let unitedMetadataQueryResponse: SearchResponse; try { - unitedMetadataQueryResponse = await esClient.search( + unitedMetadataQueryResponse = await this.esClient.search( unitedIndexQuery ); } catch (error) { @@ -404,8 +353,8 @@ export class EndpointMetadataService { const agentPolicyIds: string[] = docs.map((doc) => doc._source?.united?.agent?.policy_id ?? ''); const agentPolicies = - (await this.agentPolicyService - .getByIds(this.DANGEROUS_INTERNAL_SO_CLIENT, agentPolicyIds) + (await this.fleetServices.agentPolicy + .getByIds(this.soClient, agentPolicyIds) .catch(catchAndWrapError)) ?? []; const agentPoliciesMap = agentPolicies.reduce>( @@ -450,9 +399,7 @@ export class EndpointMetadataService { ...runtimeFields, }; - hosts.push( - await this.enrichHostMetadata(fleetServices, metadata, agent, agentPolicy, endpointPolicy) - ); + hosts.push(await this.enrichHostMetadata(metadata, agent, agentPolicy, endpointPolicy)); } } @@ -463,18 +410,12 @@ export class EndpointMetadataService { } async getAllEndpointPackagePolicies() { - return getAllEndpointPackagePolicies( - this.packagePolicyService, - this.DANGEROUS_INTERNAL_SO_CLIENT - ); + return getAllEndpointPackagePolicies(this.fleetServices.packagePolicy, this.soClient); } - async getMetadataForEndpoints( - esClient: ElasticsearchClient, - endpointIDs: string[] - ): Promise { + async getMetadataForEndpoints(endpointIDs: string[]): Promise { const query = getESQueryHostMetadataByIDs(endpointIDs); - const { body } = await esClient.search(query, { + const { body } = await this.esClient.search(query, { meta: true, }); const hosts = queryResponseToHostListResult(body); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/mocks.ts index ac97b51265d4e1..f0c5fb8d74bcdf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/mocks.ts @@ -6,30 +6,14 @@ */ import type { SavedObjectsServiceStart } from '@kbn/core/server'; -import { loggingSystemMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; -import { - createMockAgentPolicyService, - createMockAgentService, - createMockPackageService, - createPackagePolicyServiceMock, -} from '@kbn/fleet-plugin/server/mocks'; +import { coreMock, type ElasticsearchClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import type { createPackagePolicyServiceMock } from '@kbn/fleet-plugin/server/mocks'; import type { AgentPolicyServiceInterface, AgentService } from '@kbn/fleet-plugin/server'; +import { createEndpointFleetServicesFactoryMock } from '../fleet/endpoint_fleet_services_factory.mocks'; +import { createMockEndpointAppContextServiceStartContract } from '../../mocks'; import { EndpointMetadataService } from './endpoint_metadata_service'; import type { EndpointInternalFleetServicesInterface } from '../fleet/endpoint_fleet_services_factory'; -import { EndpointFleetServicesFactory } from '../fleet/endpoint_fleet_services_factory'; - -const createCustomizedPackagePolicyService = () => { - const service = createPackagePolicyServiceMock(); - service.list.mockImplementation(async (_, options) => { - return { - items: [], - total: 0, - page: options.page ?? 1, - perPage: options.perPage ?? 10, - }; - }); - return service; -}; +import { SavedObjectsClientFactory } from '../saved_objects'; /** * Endpoint Metadata Service test context. Includes an instance of `EndpointMetadataService` along with the @@ -43,44 +27,49 @@ export interface EndpointMetadataServiceTestContextMock { endpointMetadataService: EndpointMetadataService; fleetServices: EndpointInternalFleetServicesInterface; logger: ReturnType['get']>; + esClient: ElasticsearchClientMock; } -export const createEndpointMetadataServiceTestContextMock = ( - savedObjectsStart: jest.Mocked = savedObjectsServiceMock.createStartContract(), - agentService: jest.Mocked = createMockAgentService(), - agentPolicyService: jest.Mocked = createMockAgentPolicyService(), - packagePolicyService: ReturnType< - typeof createPackagePolicyServiceMock - > = createCustomizedPackagePolicyService(), - packageService: ReturnType = createMockPackageService(), - logger: ReturnType['get']> = loggingSystemMock - .create() - .get() -): EndpointMetadataServiceTestContextMock => { - const fleetServices = new EndpointFleetServicesFactory( - { - agentService, - packageService, - packagePolicyService, - agentPolicyService, - }, - savedObjectsStart - ).asInternalUser(); +export const createEndpointMetadataServiceTestContextMock = + (): EndpointMetadataServiceTestContextMock => { + const logger = loggingSystemMock.create().get(); + const { esClient, fleetStartServices, savedObjectsServiceStart } = + createMockEndpointAppContextServiceStartContract(); + const savedObjectsServiceFactory = new SavedObjectsClientFactory( + savedObjectsServiceStart, + coreMock.createSetup().http + ); + const fleetServices = createEndpointFleetServicesFactoryMock({ + fleetDependencies: fleetStartServices, + savedObjects: savedObjectsServiceFactory, + }).service.asInternalUser(); + const endpointMetadataService = new EndpointMetadataService( + esClient, + savedObjectsServiceFactory.createInternalScopedSoClient({ readonly: false }), + fleetServices, + logger + ); - const endpointMetadataService = new EndpointMetadataService( - savedObjectsStart, - agentPolicyService, - packagePolicyService, - logger - ); + fleetServices.packagePolicy.list.mockImplementation(async (_, options) => { + return { + items: [], + total: 0, + page: options.page ?? 1, + perPage: options.perPage ?? 10, + }; + }); - return { - savedObjectsStart, - agentService, - agentPolicyService, - packagePolicyService, - endpointMetadataService, - fleetServices, - logger, + return { + savedObjectsStart: savedObjectsServiceStart, + agentService: { + asInternalUser: fleetServices.agent, + asScoped: jest.fn().mockReturnValue(fleetServices.agent), + }, + agentPolicyService: fleetServices.agentPolicy, + packagePolicyService: fleetServices.packagePolicy, + logger, + endpointMetadataService, + fleetServices, + esClient: esClient as ElasticsearchClientMock, + }; }; -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/index.ts new file mode 100644 index 00000000000000..80c0b7e1d5a695 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './saved_objects_client_factory'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.mocks.ts new file mode 100644 index 00000000000000..fbe3ec4c5aa7ad --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.mocks.ts @@ -0,0 +1,41 @@ +/* + * 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 { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import type { HttpServiceSetup } from '@kbn/core/server'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import { SavedObjectsClientFactory } from './saved_objects_client_factory'; + +interface CreateSavedObjectsClientFactoryMockOptions { + savedObjectsServiceStart: jest.Mocked; + httpServiceSetup: HttpServiceSetup; +} + +export const createSavedObjectsClientFactoryMock = ( + dependencies: Partial = {} +): { + service: SavedObjectsClientFactory; + dependencies: CreateSavedObjectsClientFactoryMockOptions; +} => { + const { + savedObjectsServiceStart = savedObjectsServiceMock.createStartContract(), + httpServiceSetup = coreMock.createSetup().http, + } = dependencies; + const service = new SavedObjectsClientFactory(savedObjectsServiceStart, httpServiceSetup); + const soClient = service.createInternalUnscopedSoClient(false); + const createInternalScopedSoClientSpy = jest.spyOn(service, 'createInternalScopedSoClient'); + const createInternalUnscopedSoClientSpy = jest.spyOn(service, 'createInternalUnscopedSoClient'); + + createInternalScopedSoClientSpy.mockReturnValue(soClient); + createInternalUnscopedSoClientSpy.mockReturnValue(soClient); + + return { + service, + dependencies: { savedObjectsServiceStart, httpServiceSetup }, + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts new file mode 100644 index 00000000000000..c925c666d69570 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts @@ -0,0 +1,112 @@ +/* + * 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 @typescript-eslint/no-explicit-any,max-classes-per-file */ + +import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import type { HttpServiceSetup } from '@kbn/core/server'; +import { type SavedObjectsClientContract } from '@kbn/core/server'; +import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; +import { DEFAULT_SPACE_ID, addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import { EndpointError } from '../../../../common/endpoint/errors'; + +type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; + +const RESTRICTED_METHODS: readonly SavedObjectsClientContractKeys[] = [ + 'bulkCreate', + 'bulkUpdate', + 'create', + 'createPointInTimeFinder', + 'delete', + 'removeReferencesTo', + 'update', + 'updateObjectsSpaces', +]; + +export class InternalReadonlySoClientMethodNotAllowedError extends EndpointError {} + +/** + * Factory service for accessing saved object clients + */ +export class SavedObjectsClientFactory { + constructor( + private readonly savedObjectsServiceStart: SavedObjectsServiceStart, + private readonly httpServiceSetup: HttpServiceSetup + ) {} + + protected createFakeHttpRequest(spaceId: string = DEFAULT_SPACE_ID): CoreKibanaRequest { + const fakeRequest = CoreKibanaRequest.from({ + headers: {}, + path: '/', + route: { settings: {} }, + url: { href: {}, hash: '' } as URL, + raw: { req: { url: '/' } } as any, + }); + + if (spaceId && spaceId !== DEFAULT_SPACE_ID) { + this.httpServiceSetup.basePath.set(fakeRequest, addSpaceIdToPath('/', spaceId)); + } + + return fakeRequest; + } + + protected toReadonly(soClient: SavedObjectsClientContract): SavedObjectsClientContract { + return new Proxy(soClient, { + get( + target: SavedObjectsClientContract, + methodName: SavedObjectsClientContractKeys, + receiver: unknown + ): unknown { + if (RESTRICTED_METHODS.includes(methodName)) { + throw new InternalReadonlySoClientMethodNotAllowedError( + `Method [${methodName}] not allowed on internal readonly SO Client` + ); + } + + return Reflect.get(target, methodName, receiver); + }, + }) as SavedObjectsClientContract; + } + + /** + * Creates a SavedObjects client that is scoped to a space (default: `Default`) + */ + createInternalScopedSoClient({ + spaceId = DEFAULT_SPACE_ID, + readonly = true, + }: Partial<{ spaceId: string; readonly: boolean }> = {}): SavedObjectsClientContract { + const soClient = this.savedObjectsServiceStart.getScopedClient( + this.createFakeHttpRequest(spaceId), + { excludedExtensions: [SECURITY_EXTENSION_ID] } + ); + + if (readonly) { + return this.toReadonly(soClient); + } + + return soClient; + } + + /** + * Create a SavedObjects client that is un-scoped to a space and thus can access all + * data across all spaces. + * + * **WARNING:** Use with care! + */ + createInternalUnscopedSoClient(readonly: boolean = true): SavedObjectsClientContract { + const soClient = this.savedObjectsServiceStart.getScopedClient(this.createFakeHttpRequest(), { + excludedExtensions: [SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID], + }); + + if (readonly) { + return this.toReadonly(soClient); + } + + return soClient; + } +} diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.test.ts deleted file mode 100644 index 56d53969545361..00000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { savedObjectsServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; -import type { SavedObjectsClientContract } from '@kbn/core/server'; -import { - createInternalReadonlySoClient, - InternalReadonlySoClientMethodNotAllowedError, -} from './create_internal_readonly_so_client'; - -describe('When using the `createInternalReadonlySoClient`', () => { - let realSoClientMock: ReturnType; - let readonlySoClient: ReturnType; - - beforeEach(() => { - const soStartContract = savedObjectsServiceMock.createStartContract(); - realSoClientMock = savedObjectsClientMock.create(); - soStartContract.getScopedClient.mockReturnValue(realSoClientMock); - readonlySoClient = createInternalReadonlySoClient(soStartContract); - }); - - it.each([ - 'get', - 'bulkGet', - 'checkConflicts', - 'collectMultiNamespaceReferences', - 'find', - 'resolve', - ])('should allow usage of %s() method', (methodName) => { - expect(() => readonlySoClient[methodName]).not.toThrow(); - }); - - it.each([ - 'bulkCreate', - 'bulkUpdate', - 'create', - 'createPointInTimeFinder', - 'delete', - 'removeReferencesTo', - 'openPointInTimeForType', - 'closePointInTime', - 'update', - 'updateObjectsSpaces', - ])('should throw if usage of %s() is attempted', (methodName) => { - expect(() => readonlySoClient[methodName]).toThrow( - InternalReadonlySoClientMethodNotAllowedError - ); - }); -}); diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts deleted file mode 100644 index b621222e79c0ab..00000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { SavedObjectsClientContract, SavedObjectsServiceStart } from '@kbn/core/server'; -import { createInternalSoClient } from './create_internal_so_client'; -import { EndpointError } from '../../../common/endpoint/errors'; - -type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; - -const RESTRICTED_METHODS: readonly SavedObjectsClientContractKeys[] = [ - 'bulkCreate', - 'bulkUpdate', - 'create', - 'createPointInTimeFinder', - 'delete', - 'removeReferencesTo', - 'openPointInTimeForType', - 'closePointInTime', - 'update', - 'updateObjectsSpaces', -]; - -export class InternalReadonlySoClientMethodNotAllowedError extends EndpointError {} - -/** - * Creates an internal (system user) Saved Objects client (permissions turned off) that can only perform READ - * operations. - */ -export const createInternalReadonlySoClient = ( - savedObjectsServiceStart: SavedObjectsServiceStart -): SavedObjectsClientContract => { - const internalSoClient = createInternalSoClient(savedObjectsServiceStart); - - return new Proxy(internalSoClient, { - get( - target: SavedObjectsClientContract, - methodName: SavedObjectsClientContractKeys, - receiver: unknown - ): unknown { - if (RESTRICTED_METHODS.includes(methodName)) { - throw new InternalReadonlySoClientMethodNotAllowedError( - `Method [${methodName}] not allowed on internal readonly SO Client` - ); - } - - return Reflect.get(target, methodName, receiver); - }, - }) as SavedObjectsClientContract; -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts deleted file mode 100644 index 88e0d7a70a4c39..00000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; -import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; - -export const createInternalSoClient = ( - savedObjectsServiceStart: SavedObjectsServiceStart -): SavedObjectsClientContract => { - const fakeRequest = { - headers: {}, - getBasePath: () => '', - path: '/', - route: { settings: {} }, - url: { href: {} }, - raw: { req: { url: '/' } }, - } as unknown as KibanaRequest; - - return savedObjectsServiceStart.getScopedClient(fakeRequest, { - excludedExtensions: [SECURITY_EXTENSION_ID], - }); -}; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index c4e32f050c1d7d..81bfbf1cdd9790 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -10,7 +10,6 @@ import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types import { elasticsearchServiceMock, httpServerMock, - loggingSystemMock, savedObjectsClientMock, } from '@kbn/core/server/mocks'; import { @@ -76,13 +75,15 @@ import type { PostAgentPolicyCreateCallback, PutPackagePolicyUpdateCallback, } from '@kbn/fleet-plugin/server/types'; +import type { EndpointMetadataService } from '../endpoint/services/metadata'; +import { createEndpointMetadataServiceTestContextMock } from '../endpoint/services/metadata/mocks'; jest.mock('uuid', () => ({ v4: (): string => 'NEW_UUID', })); describe('ingest_integration tests ', () => { - let endpointAppContextMock: EndpointAppContextServiceStartContract; + let endpointAppContextStartContract: EndpointAppContextServiceStartContract; let req: KibanaRequest; let ctx: ReturnType; const exceptionListClient: ExceptionListClient = getExceptionListClientMock(); @@ -100,18 +101,24 @@ describe('ingest_integration tests ', () => { const generator = new EndpointDocGenerator(); const cloudService = cloudMock.createSetup(); let productFeaturesService: ProductFeaturesService; + let endpointMetadataService: EndpointMetadataService; + let logger: Logger; beforeEach(() => { - endpointAppContextMock = createMockEndpointAppContextServiceStartContract(); + endpointAppContextStartContract = createMockEndpointAppContextServiceStartContract(); ctx = requestContextMock.createTools().context; req = httpServerMock.createKibanaRequest(); licenseEmitter = new Subject(); licenseService = new LicenseService(); licenseService.start(licenseEmitter); - productFeaturesService = endpointAppContextMock.productFeaturesService; + productFeaturesService = endpointAppContextStartContract.productFeaturesService; + + const metadataMocks = createEndpointMetadataServiceTestContextMock(); + logger = metadataMocks.logger; + endpointMetadataService = metadataMocks.endpointMetadataService; jest - .spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy') + .spyOn(endpointMetadataService, 'getFleetEndpointPackagePolicy') .mockResolvedValue(createMockPolicyData()); }); @@ -156,12 +163,11 @@ describe('ingest_integration tests ', () => { }); const invokeCallback = async (manifestManager: ManifestManager): Promise => { - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyCreateCallback( logger, manifestManager, requestContextFactoryMock.create(), - endpointAppContextMock.alerting, + endpointAppContextStartContract.alerting, licenseService, exceptionListClient, cloudService, @@ -346,7 +352,6 @@ describe('ingest_integration tests ', () => { describe('package policy post create callback', () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyPostCreateCallback(logger, exceptionListClient); const policyConfig = generator.generatePolicyPackagePolicy() as PackagePolicy; @@ -420,8 +425,6 @@ describe('ingest_integration tests ', () => { describe('agent policy update callback', () => { it('ProductFeature disabled - returns an error if higher tier features are turned on in the policy', async () => { - const logger = loggingSystemMock.create().get('ingest_integration.test'); - productFeaturesService = createProductFeaturesServiceMock( ALL_PRODUCT_FEATURE_KEYS.filter( (key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection @@ -437,8 +440,6 @@ describe('ingest_integration tests ', () => { ); }); it('ProductFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => { - const logger = loggingSystemMock.create().get('ingest_integration.test'); - productFeaturesService = createProductFeaturesServiceMock( ALL_PRODUCT_FEATURE_KEYS.filter( (key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection @@ -453,8 +454,6 @@ describe('ingest_integration tests ', () => { expect(updatedPolicyConfig).toEqual(policyConfig); }); it('ProductFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => { - const logger = loggingSystemMock.create().get('ingest_integration.test'); - const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService); const policyConfig = generator.generateAgentPolicy(); @@ -465,8 +464,6 @@ describe('ingest_integration tests ', () => { expect(updatedPolicyConfig).toEqual(policyConfig); }); it('ProductFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => { - const logger = loggingSystemMock.create().get('ingest_integration.test'); - const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService); const policyConfig = generator.generateAgentPolicy(); @@ -477,12 +474,10 @@ describe('ingest_integration tests ', () => { }); describe('agent policy create callback', () => { - let logger: Logger; let callback: PostAgentPolicyCreateCallback; let policyConfig: GetAgentPoliciesResponseItem; beforeEach(() => { - logger = loggingSystemMock.create().get('ingest_integration.test'); callback = getAgentPolicyCreateCallback(logger, productFeaturesService); policyConfig = generator.generateAgentPolicy(); }); @@ -537,12 +532,11 @@ describe('ingest_integration tests ', () => { }); it('returns an error if paid features are turned on in the policy', async () => { const mockPolicy = policyFactory(); // defaults with paid features on - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -558,12 +552,11 @@ describe('ingest_integration tests ', () => { it('updates successfully if no paid features are turned on in the policy', async () => { const mockPolicy = policyFactoryWithoutPaidFeatures(); mockPolicy.windows.malware.mode = ProtectionModes.detect; - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -594,10 +587,10 @@ describe('ingest_integration tests ', () => { ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates') ); const callback = getPackagePolicyUpdateCallback( - endpointAppContextMock.logger, + logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -649,12 +642,11 @@ describe('ingest_integration tests ', () => { 'should return bad request for invalid endpoint package policy global manifest values', async ({ date, message }) => { const mockPolicy = policyFactory(); // defaults with paid features on - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -715,12 +707,11 @@ describe('ingest_integration tests ', () => { 'should return bad request for invalid endpoint package policy global manifest values', async ({ date, message }) => { const mockPolicy = policyFactory(); // defaults with paid features on - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -759,12 +750,11 @@ describe('ingest_integration tests ', () => { it('updates successfully when paid features are turned on', async () => { const mockPolicy = policyFactory(); mockPolicy.windows.popup.malware.message = 'paid feature'; - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -786,10 +776,10 @@ describe('ingest_integration tests ', () => { ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ); const callback = getPackagePolicyUpdateCallback( - endpointAppContextMock.logger, + logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -864,12 +854,11 @@ describe('ingest_integration tests ', () => { mockPolicy.meta.license_uuid = 'updated-uid'; mockPolicy.meta.serverless = false; mockPolicy.meta.billable = false; - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -903,12 +892,11 @@ describe('ingest_integration tests ', () => { mockPolicy.meta.license_uuid = 'updated-uid'; mockPolicy.meta.serverless = false; mockPolicy.meta.billable = false; - const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -936,7 +924,6 @@ describe('ingest_integration tests ', () => { describe('when `antivirus_registration.mode` is changed', () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const logger = loggingSystemMock.create().get('ingest_integration.test'); let callback: PutPackagePolicyUpdateCallback; let inputPolicyConfig: PolicyData; let inputWindowsConfig: PolicyConfig['windows']; @@ -950,8 +937,8 @@ describe('ingest_integration tests ', () => { callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService @@ -1044,14 +1031,13 @@ describe('ingest_integration tests ', () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const logger = loggingSystemMock.create().get('ingest_integration.test'); licenseEmitter.next(Enterprise); const callback = getPackagePolicyUpdateCallback( logger, licenseService, - endpointAppContextMock.featureUsageService, - endpointAppContextMock.endpointMetadataService, + endpointAppContextStartContract.featureUsageService, + endpointMetadataService, cloudService, esClient, productFeaturesService diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts index e5bae990528031..5b4eab27f71ab5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts @@ -41,7 +41,7 @@ export const getAllIntegrationsRoute = (router: SecuritySolutionPluginRouter) => const [packages, packagePolicies] = await Promise.all([ fleet.packages.getPackages(), - fleet.packagePolicy.list(fleet.internalReadonlySoClient, {}), + fleet.packagePolicy.list(fleet.savedObjects.createInternalScopedSoClient(), {}), ]); // Elastic prebuilt rules is a special package and should be skipped const packagesWithoutPrebuiltSecurityRules = packages.filter( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts index 407c7d54adc526..3a3d159d1337f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts @@ -46,7 +46,7 @@ export const getInstalledIntegrationsRoute = (router: SecuritySolutionPluginRout }); const packagePolicies = await fleet.packagePolicy.list( - fleet.internalReadonlySoClient, + fleet.savedObjects.createInternalScopedSoClient(), {} ); packagePolicies.items.forEach((policy) => { diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts index 8c3dfcd4bc27ef..0f5da118ac2ae8 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts @@ -37,8 +37,8 @@ describe('When using Artifacts Exceptions BaseValidator', () => { const servicesStart = createMockEndpointAppContextServiceStartContract(); - packagePolicyService = servicesStart.endpointFleetServicesFactory.asInternalUser() - .packagePolicy as jest.Mocked; + packagePolicyService = servicesStart.fleetStartServices + .packagePolicyService as jest.Mocked; endpointAppContextServices = new EndpointAppContextService(); endpointAppContextServices.setup(createMockEndpointAppContextServiceSetupContract()); @@ -48,7 +48,9 @@ describe('When using Artifacts Exceptions BaseValidator', () => { if (withNoAuth) { const fleetAuthz = createFleetAuthzMock(); fleetAuthz.fleet.all = false; - (servicesStart.fleetAuthzService?.fromRequest as jest.Mock).mockResolvedValue(fleetAuthz); + (servicesStart.fleetStartServices.authz.fromRequest as jest.Mock).mockResolvedValue( + fleetAuthz + ); (servicesStart.security.authc.getCurrentUser as jest.Mock).mockReturnValue( securityMock.createMockAuthenticatedUser() ); diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts index 4d92f5f2c9dd80..4ad1d3f699990d 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts @@ -140,15 +140,15 @@ export class BaseValidator { */ protected async validateByPolicyItem(item: ExceptionItemLikeOptions): Promise { if (this.isItemByPolicy(item)) { - const { packagePolicy, internalReadonlySoClient } = - this.endpointAppContext.getInternalFleetServices(); + const { packagePolicy, savedObjects } = this.endpointAppContext.getInternalFleetServices(); const policyIds = getPolicyIdsFromArtifact(item); + const soClient = savedObjects.createInternalScopedSoClient(); if (policyIds.length === 0) { return; } - const policiesFromFleet = await packagePolicy.getByIDs(internalReadonlySoClient, policyIds, { + const policiesFromFleet = await packagePolicy.getByIDs(soClient, policyIds, { ignoreMissing: true, }); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0f442564d44873..25a133b278cc9f 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -76,7 +76,6 @@ import { PolicyWatcher } from './endpoint/lib/policy/license_watch'; import previewPolicy from './lib/detection_engine/routes/index/preview_policy.json'; import type { IRuleMonitoringService } from './lib/detection_engine/rule_monitoring'; import { createRuleMonitoringService } from './lib/detection_engine/rule_monitoring'; -import { EndpointMetadataService } from './endpoint/services/metadata'; import type { CreateRuleAdditionalOptions, CreateRuleOptions, @@ -103,7 +102,6 @@ import type { SecuritySolutionPluginStart, SecuritySolutionPluginStartDependencies, } from './plugin_contract'; -import { EndpointFleetServicesFactory } from './endpoint/services/fleet'; import { featureUsageService } from './endpoint/services/feature_usage'; import { setIsElasticCloudDeployment } from './lib/telemetry/helpers'; import { artifactService } from './lib/telemetry/artifact'; @@ -238,6 +236,7 @@ export class Plugin implements ISecuritySolutionPlugin { securitySolutionRequestContextFactory: requestContextFactory, cloud: plugins.cloud, loggerFactory: this.pluginContext.logger, + httpServiceSetup: core.http, }); initUsageCollectors({ @@ -525,27 +524,10 @@ export class Plugin implements ISecuritySolutionPlugin { // from where authz can be derived) false ); - const { - authz, - agentService, - packageService, - packagePolicyService, - agentPolicyService, - createFilesClient, - createFleetActionsClient, - } = - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - plugins.fleet!; - let manifestManager: ManifestManager | undefined; - const endpointFleetServicesFactory = new EndpointFleetServicesFactory( - { - agentService, - packageService, - packagePolicyService, - agentPolicyService, - }, - core.savedObjects - ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const fleetStartServices = plugins.fleet!; + + const { packageService } = fleetStartServices; this.licensing$ = plugins.licensing.license$; @@ -562,26 +544,42 @@ export class Plugin implements ISecuritySolutionPlugin { plugins.elasticAssistant.registerFeatures(APP_UI_ID, features); plugins.elasticAssistant.registerFeatures('management', features); + const manifestManager = new ManifestManager({ + savedObjectsClient, + exceptionListClient, + artifactClient: new EndpointArtifactClient( + fleetStartServices.createArtifactsClient('endpoint') + ), + packagePolicyService: fleetStartServices.packagePolicyService, + logger: this.pluginContext.logger.get('ManifestManager'), + experimentalFeatures: config.experimentalFeatures, + packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize, + esClient: core.elasticsearch.client.asInternalUser, + productFeaturesService, + }); + + this.endpointAppContextService.start({ + fleetStartServices, + security: core.security, + alerting: plugins.alerting, + config, + cases: plugins.cases, + manifestManager, + licenseService, + exceptionListsClient: exceptionListClient, + registerListsServerExtension: this.lists?.registerExtension, + featureUsageService, + experimentalFeatures: config.experimentalFeatures, + esClient: core.elasticsearch.client.asInternalUser, + productFeaturesService, + savedObjectsServiceStart: core.savedObjects, + connectorActions: plugins.actions, + }); + if (this.lists && plugins.taskManager && plugins.fleet) { // Exceptions, Artifacts and Manifests start const taskManager = plugins.taskManager; - const artifactClient = new EndpointArtifactClient( - plugins.fleet.createArtifactsClient('endpoint') - ); - manifestManager = new ManifestManager({ - savedObjectsClient, - artifactClient, - exceptionListClient, - packagePolicyService: plugins.fleet.packagePolicyService, - logger: this.pluginContext.logger.get('ManifestManager'), - experimentalFeatures: config.experimentalFeatures, - packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize, - esClient: core.elasticsearch.client.asInternalUser, - productFeaturesService, - }); - - // Migrate artifacts to fleet and then start the manifest task after that is done plugins.fleet .fleetSetupCompleted() .then(async () => { @@ -594,24 +592,24 @@ export class Plugin implements ISecuritySolutionPlugin { logger.error(new Error('User artifacts task not available.')); } + const fleetServices = this.endpointAppContextService.getInternalFleetServices(); + await turnOffPolicyProtectionsIfNotSupported( core.elasticsearch.client.asInternalUser, - endpointFleetServicesFactory.asInternalUser(), + fleetServices, productFeaturesService, logger ); - await turnOffAgentPolicyFeatures( - endpointFleetServicesFactory.asInternalUser(), - productFeaturesService, - logger - ); + await turnOffAgentPolicyFeatures(fleetServices, productFeaturesService, logger); }) .catch(() => {}); // License related start licenseService.start(this.licensing$); + featureUsageService.start(plugins.licensing); + this.policyWatcher = new PolicyWatcher( plugins.fleet.packagePolicyService, core.savedObjects, @@ -621,36 +619,6 @@ export class Plugin implements ISecuritySolutionPlugin { this.policyWatcher.start(licenseService); } - this.endpointAppContextService.start({ - fleetAuthzService: authz, - createFleetFilesClient: createFilesClient, - endpointMetadataService: new EndpointMetadataService( - core.savedObjects, - agentPolicyService, - packagePolicyService, - logger - ), - endpointFleetServicesFactory, - security: core.security, - alerting: plugins.alerting, - config, - cases: plugins.cases, - logger, - manifestManager, - registerIngestCallback, - licenseService, - exceptionListsClient: exceptionListClient, - registerListsServerExtension: this.lists?.registerExtension, - featureUsageService, - experimentalFeatures: config.experimentalFeatures, - messageSigningService: plugins.fleet?.messageSigningService, - createFleetActionsClient, - esClient: core.elasticsearch.client.asInternalUser, - productFeaturesService, - savedObjectsClient, - connectorActions: plugins.actions, - }); - if (plugins.taskManager) { this.completeExternalResponseActionsTask .start({ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index 4472fa3c0d6913..94d45b2b63e0e8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -170,14 +170,13 @@ export const getHostEndpoint = async ( const logger = endpointContext.logFactory.get('metadata'); try { - const fleetServices = endpointContext.service.getInternalFleetServices(); const endpointMetadataService = 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, fleetServices, id); + .getEnrichedHostMetadata(id); const fleetAgentId = endpointData.metadata.elastic.agent.id; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index e33ecb852b5b17..0817d3f71f6257 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -226,5 +226,7 @@ "@kbn/entityManager-plugin", "@kbn/entities-schema", "@kbn/inference-plugin", + "@kbn/core-saved-objects-server-mocks", + "@kbn/core-http-router-server-internal", ] }