From 368eba6ee9d3e8620c262386a5b51a2b34651571 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 23 Aug 2022 18:19:11 -0400 Subject: [PATCH] [Fleet] Remove field package_policies from agent policy (#138677) --- .../server/deprecations/deprecations.test.ts | 2 +- .../server/lib/fleet_util.ts | 5 +- .../server/routes/benchmarks/benchmarks.ts | 11 +- x-pack/plugins/fleet/common/index.ts | 2 + .../fleet/common/services/limited_package.ts | 6 +- .../plugins/fleet/common/services/routes.ts | 4 + .../fleet/common/types/models/agent_policy.ts | 2 +- .../fleet/cypress/downloads/downloads.html | Bin 0 -> 4956 bytes x-pack/plugins/fleet/cypress/tasks/fleet.ts | 6 + .../components/package_policies/index.tsx | 6 +- .../sections/agent_policy/list_page/index.tsx | 1 + .../sections/agents/agent_list_page/index.tsx | 15 +- .../epm/screens/detail/index.test.tsx | 8 +- .../use_package_policies_with_agent_policy.ts | 35 ++--- .../hooks/use_package_installations.tsx | 4 +- .../fleet/public/services/has_fleet_server.ts | 9 +- .../reset_preconfiguration.test.ts | 5 +- x-pack/plugins/fleet/server/mocks/index.ts | 1 + .../server/routes/agent_policy/handlers.ts | 1 + .../fleet/server/saved_objects/index.ts | 3 +- .../saved_objects/migrations/to_v7_10_0.ts | 9 +- .../saved_objects/migrations/to_v8_5_0.ts | 22 +++ ...kage_policies_to_agent_permissions.test.ts | 6 +- .../package_policies_to_agent_permissions.ts | 12 +- .../server/services/agent_policy.test.ts | 11 +- .../fleet/server/services/agent_policy.ts | 92 ++---------- .../fleet/server/services/package_policy.ts | 102 +++++++++---- .../server/services/preconfiguration.test.ts | 11 +- .../fleet_agent_policy_generator.ts | 1 - .../common/endpoint/generate_data.ts | 1 - .../public/management/mocks/fleet_mocks.ts | 141 +++++++++++++++++- .../management/pages/endpoint_hosts/mocks.ts | 6 + .../endpoint_hosts/store/middleware.test.ts | 1 + .../pages/endpoint_hosts/store/middleware.ts | 20 +-- .../store/mock_endpoint_result_list.ts | 22 ++- .../pages/endpoint_hosts/view/index.test.tsx | 2 + .../pages/policy/view/policy_list.test.tsx | 20 +-- .../pages/policy/view/policy_list.tsx | 24 +-- .../management/services/policies/hooks.ts | 16 +- .../management/services/policies/ingest.ts | 41 +++++ .../metadata/endpoint_metadata_service.ts | 9 +- 41 files changed, 445 insertions(+), 250 deletions(-) create mode 100644 x-pack/plugins/fleet/cypress/downloads/downloads.html create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_5_0.ts diff --git a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts index b57628a3f326b..1843affb9bfc3 100644 --- a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts +++ b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts @@ -54,7 +54,7 @@ describe('getDeprecations', () => { get: () => ({ id: 'foo', - package_policies: [''], + package_policies: [{ package: { name: 'system' } }], } as AgentPolicy), }, }), diff --git a/x-pack/plugins/cloud_security_posture/server/lib/fleet_util.ts b/x-pack/plugins/cloud_security_posture/server/lib/fleet_util.ts index 42e11f08d9d80..9ef90e4175a6c 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/fleet_util.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/fleet_util.ts @@ -58,7 +58,10 @@ export const getCspAgentPolicies = async ( packagePolicies: PackagePolicy[], agentPolicyService: AgentPolicyServiceInterface ): Promise => - agentPolicyService.getByIds(soClient, uniq(map(packagePolicies, 'policy_id'))); + agentPolicyService.getByIds(soClient, uniq(map(packagePolicies, 'policy_id')), { + withPackagePolicies: true, + ignoreMissing: true, + }); export const getCspPackagePolicies = ( soClient: SavedObjectsClientContract, diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts index 8ffeb38b6b576..4bc00f5e0209d 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts @@ -111,11 +111,12 @@ const createBenchmarks = ( ); return Promise.all( agentPolicies.flatMap((agentPolicy) => { - const cspPackagesOnAgent = agentPolicy.package_policies - .map((pckPolicyId) => { - if (typeof pckPolicyId === 'string') return cspPackagePoliciesMap.get(pckPolicyId); - }) - .filter(isNonNullable); + const cspPackagesOnAgent = + agentPolicy.package_policies + ?.map(({ id: pckPolicyId }) => { + return cspPackagePoliciesMap.get(pckPolicyId); + }) + .filter(isNonNullable) ?? []; const benchmarks = cspPackagesOnAgent.map(async (cspPackage) => { const cspRulesStatus = await addPackagePolicyCspRules(soClient, cspPackage); const agentPolicyStatus = { diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts index ced5c17f55f6f..d514d5b564690 100644 --- a/x-pack/plugins/fleet/common/index.ts +++ b/x-pack/plugins/fleet/common/index.ts @@ -101,6 +101,8 @@ export type { UpgradePackagePolicyResponseItem, UpgradePackagePolicyBaseResponse, UpgradePackagePolicyDryRunResponseItem, + BulkGetPackagePoliciesResponse, + BulkGetAgentPoliciesResponse, // Models Agent, AgentStatus, diff --git a/x-pack/plugins/fleet/common/services/limited_package.ts b/x-pack/plugins/fleet/common/services/limited_package.ts index 601f680c8bf03..e4e9dd090941d 100644 --- a/x-pack/plugins/fleet/common/services/limited_package.ts +++ b/x-pack/plugins/fleet/common/services/limited_package.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PackageInfo, AgentPolicy, PackagePolicy } from '../types'; +import type { PackageInfo, AgentPolicy } from '../types'; export const isPackageLimited = (packageInfo: PackageInfo): boolean => { return (packageInfo.policy_templates || []).some( @@ -17,10 +17,10 @@ export const doesAgentPolicyAlreadyIncludePackage = ( agentPolicy: AgentPolicy, packageName: string ): boolean => { - if (agentPolicy.package_policies.length && typeof agentPolicy.package_policies[0] === 'string') { + if (!agentPolicy.package_policies) { throw new Error('Unable to read full package policy information'); } - return (agentPolicy.package_policies as PackagePolicy[]) + return agentPolicy.package_policies .map((packagePolicy) => packagePolicy.package?.name || '') .includes(packageName); }; diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index 95551ddeea7c0..323d7d1f8b378 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -118,6 +118,10 @@ export const agentPolicyRouteService = { return AGENT_POLICY_API_ROUTES.LIST_PATTERN; }, + getBulkGetPath: () => { + return AGENT_POLICY_API_ROUTES.BULK_GET_PATTERN; + }, + getInfoPath: (agentPolicyId: string) => { return AGENT_POLICY_API_ROUTES.INFO_PATTERN.replace('{agentPolicyId}', agentPolicyId); }, diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 44e6b35c02eec..ea22f73a2e5f9 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -35,7 +35,7 @@ export interface NewAgentPolicy { export interface AgentPolicy extends Omit { id: string; status: ValueOf; - package_policies: string[] | PackagePolicy[]; + package_policies?: PackagePolicy[]; is_managed: boolean; // required for created policy updated_at: string; updated_by: string; diff --git a/x-pack/plugins/fleet/cypress/downloads/downloads.html b/x-pack/plugins/fleet/cypress/downloads/downloads.html new file mode 100644 index 0000000000000000000000000000000000000000..772778ea352e58515ffdccdd56b8f8f73a6b15c5 GIT binary patch literal 4956 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 agentPolicy }) => { useBreadcrumbs('policy_details', { policyName: agentPolicy.name }); - if (agentPolicy.package_policies.length === 0) { + if (!agentPolicy.package_policies || agentPolicy.package_policies.length === 0) { return ; } return ( ); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx index ba6b7f4ad31c4..9b0d9051a32ed 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx @@ -89,6 +89,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { sortField: sorting?.field, sortOrder: sorting?.direction, kuery: search, + full: true, }); // Some policies retrieved, set up table props diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index a9e3429732b3c..6bf9506d6ba28 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -19,7 +19,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; -import type { Agent, AgentPolicy, PackagePolicy, SimplifiedAgentStatus } from '../../../types'; +import type { Agent, AgentPolicy, SimplifiedAgentStatus } from '../../../types'; import { usePagination, useAuthz, @@ -35,8 +35,12 @@ import { sendGetAgentTags, } from '../../../hooks'; import { AgentEnrollmentFlyout, AgentPolicySummaryLine } from '../../../components'; -import { AgentStatusKueryHelper, isAgentUpgradeable } from '../../../services'; -import { AGENTS_PREFIX, FLEET_SERVER_PACKAGE, SO_SEARCH_LIMIT } from '../../../constants'; +import { + AgentStatusKueryHelper, + isAgentUpgradeable, + policyHasFleetServer, +} from '../../../services'; +import { AGENTS_PREFIX, SO_SEARCH_LIMIT } from '../../../constants'; import { AgentReassignAgentPolicyModal, AgentHealth, @@ -389,10 +393,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { return false; } - return agentPolicy.package_policies.some( - (ap: string | PackagePolicy) => - typeof ap !== 'string' && ap.package?.name === FLEET_SERVER_PACKAGE - ); + return policyHasFleetServer(agentPolicy); }, [agentToUnenroll, agentPoliciesIndexedById]); // Fleet server unhealthy status diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index a77c45a6b5110..693dee99ebf69 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -717,11 +717,7 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos namespace: 'default', description: 'Default agent policy created by Kibana', status: 'active', - package_policies: [ - '4d09bd78-b0ad-4238-9fa3-d87d3c887c73', - '2babac18-eb8e-4ce4-b53b-4b7c5f507019', - 'e8a37031-2907-44f6-89d2-98bd493f60dc', - ], + package_policies: [], is_managed: false, monitoring_enabled: ['logs', 'metrics'], revision: 6, @@ -735,7 +731,7 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos namespace: 'default', description: 'Protect EU from COVID', status: 'active', - package_policies: ['e8a37031-2907-44f6-89d2-98bd493f60cd'], + package_policies: [], is_managed: false, monitoring_enabled: ['logs', 'metrics'], revision: 2, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts index 4f6f456bf82cd..399016ebf2042 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts @@ -14,7 +14,6 @@ import type { GetPackagePoliciesResponse, } from '../../../../../types'; import { agentPolicyRouteService } from '../../../../../services'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { useGetPackagePolicies, useConditionalRequest } from '../../../../../hooks'; import type { SendConditionalRequestConfig } from '../../../../../hooks'; @@ -52,37 +51,31 @@ export const usePackagePoliciesWithAgentPolicy = ( resendRequest, } = useGetPackagePolicies(query); - const agentPoliciesFilter = useMemo(() => { + const agentPoliciesIds = useMemo(() => { if (!packagePoliciesData?.items.length) { - return ''; + return []; } // Build a list of package_policies for which we need Agent Policies for. Since some package // policies can exist within the same Agent Policy, we don't need to (in some cases) include // the entire list of package_policy ids. - const includedAgentPolicies = new Set(); - - return `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies: (${packagePoliciesData.items - .filter((packagePolicy) => { - if (includedAgentPolicies.has(packagePolicy.policy_id)) { - return false; - } - includedAgentPolicies.add(packagePolicy.policy_id); - return true; - }) - .map((packagePolicy) => packagePolicy.id) - .join(' or ')}) `; + return Array.from( + new Set( + packagePoliciesData.items.map((packagePolicy) => packagePolicy.policy_id) + ).values() + ); }, [packagePoliciesData]); const { data: agentPoliciesData, isLoading: isLoadingAgentPolicies } = useConditionalRequest({ - path: agentPolicyRouteService.getListPath(), - method: 'get', - query: { - perPage: 100, - kuery: agentPoliciesFilter, + path: agentPolicyRouteService.getBulkGetPath(), + method: 'post', + body: { + ids: agentPoliciesIds, + full: true, + ignoreMissing: true, }, - shouldSendRequest: !!packagePoliciesData?.items.length, + shouldSendRequest: agentPoliciesIds.length > 0, } as SendConditionalRequestConfig); const [enrichedData, setEnrichedData] = useState(); diff --git a/x-pack/plugins/fleet/public/hooks/use_package_installations.tsx b/x-pack/plugins/fleet/public/hooks/use_package_installations.tsx index 4789770b7046f..5a0b6285c71db 100644 --- a/x-pack/plugins/fleet/public/hooks/use_package_installations.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_package_installations.tsx @@ -52,8 +52,8 @@ export const usePackageInstallations = () => { const updatableIntegrations = useMemo>( () => (agentPolicyData?.items || []).reduce((result, policy) => { - policy.package_policies.forEach((pkgPolicy: PackagePolicy | string) => { - if (typeof pkgPolicy === 'string' || !pkgPolicy.package) return false; + policy.package_policies?.forEach((pkgPolicy: PackagePolicy) => { + if (!pkgPolicy.package) return false; const { name, version } = pkgPolicy.package; const installedPackage = allInstalledPackages.find( (installedPkg) => diff --git a/x-pack/plugins/fleet/public/services/has_fleet_server.ts b/x-pack/plugins/fleet/public/services/has_fleet_server.ts index e1100d6447aa2..43724d121b90f 100644 --- a/x-pack/plugins/fleet/public/services/has_fleet_server.ts +++ b/x-pack/plugins/fleet/public/services/has_fleet_server.ts @@ -9,8 +9,11 @@ import { FLEET_SERVER_PACKAGE } from '../constants'; import type { AgentPolicy, PackagePolicy } from '../types'; export function policyHasFleetServer(agentPolicy: AgentPolicy) { - return agentPolicy.package_policies?.some( - (ap: string | PackagePolicy) => - typeof ap !== 'string' && ap.package?.name === FLEET_SERVER_PACKAGE + if (!agentPolicy.package_policies) { + return false; + } + + return agentPolicy.package_policies.some( + (ap: PackagePolicy) => ap.package?.name === FLEET_SERVER_PACKAGE ); } diff --git a/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts b/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts index 30bdf452cde12..b4afbec067874 100644 --- a/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts @@ -248,9 +248,7 @@ describe('Fleet preconfiguration reset', () => { it('Works if the preconfigured policies already exists with a missing package policy', async () => { const soClient = kbnServer.coreStart.savedObjects.createInternalRepository(); - await soClient.update('ingest-agent-policies', POLICY_ID, { - package_policies: [], - }); + await soClient.update('ingest-agent-policies', POLICY_ID, {}); const resetAPI = getSupertestWithAdminUser( kbnServer.root, @@ -268,7 +266,6 @@ describe('Fleet preconfiguration reset', () => { expect.arrayContaining([ expect.objectContaining({ name: 'Elastic Cloud agent policy 0001', - package_policies: expect.arrayContaining([expect.stringMatching(/.*/)]), }), expect.objectContaining({ name: 'Second preconfigured policy', diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index b4e5f83d2cb06..a76988506cad2 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -121,6 +121,7 @@ export const createPackagePolicyServiceMock = (): jest.Mocked & { - package_configs: string[] | PackagePolicy[]; + Omit & { + package_configs: string[]; + package_policies?: string[]; }, - AgentPolicy + Omit & { + package_policies?: string[]; + } > = (agentPolicyDoc) => { agentPolicyDoc.attributes.package_policies = agentPolicyDoc.attributes.package_configs; // @ts-expect-error diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_5_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_5_0.ts new file mode 100644 index 0000000000000..9cbcc85c46936 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_5_0.ts @@ -0,0 +1,22 @@ +/* + * 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 { SavedObjectMigrationFn } from '@kbn/core/server'; + +import type { AgentPolicy } from '../../types'; + +export const migrateAgentPolicyToV850: SavedObjectMigrationFn< + Exclude & { + package_policies: string[]; + }, + AgentPolicy +> = (agentPolicyDoc) => { + // @ts-expect-error + delete agentPolicyDoc.attributes.package_policies; + + return agentPolicyDoc; +}; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts index 0db543e4357a4..a60b5c68d05a9 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts @@ -31,8 +31,10 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { expect(permissions).toBeUndefined(); }); - it('Throw an error for string package policies', async () => { - await expect(() => storedPackagePoliciesToAgentPermissions(soClient, ['foo'])).rejects.toThrow( + it('Throw an error if package policies is not an array', async () => { + await expect(() => + storedPackagePoliciesToAgentPermissions(soClient, undefined) + ).rejects.toThrow( /storedPackagePoliciesToAgentPermissions should be called with a PackagePolicy/ ); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts index ee17494ca6454..b961eb8691f88 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts @@ -20,19 +20,19 @@ export const DEFAULT_CLUSTER_PERMISSIONS = ['monitor']; export async function storedPackagePoliciesToAgentPermissions( soClient: SavedObjectsClientContract, - packagePolicies: string[] | PackagePolicy[] + packagePolicies?: PackagePolicy[] ): Promise { - if (packagePolicies.length === 0) { - return; - } - // I'm not sure what permissions to return for this case, so let's return the defaults - if (typeof packagePolicies[0] === 'string') { + if (!packagePolicies) { throw new Error( 'storedPackagePoliciesToAgentPermissions should be called with a PackagePolicy' ); } + if (packagePolicies.length === 0) { + return; + } + const permissionEntries = (packagePolicies as PackagePolicy[]).map>( async (packagePolicy) => { if (!packagePolicy.package) { diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index 4d348af471772..8dd8c885af3e4 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -75,6 +75,8 @@ const mockedOutputService = outputService as jest.Mocked; const mockedDownloadSourceService = downloadSourceService as jest.Mocked< typeof downloadSourceService >; +const mockedPackagePolicyService = packagePolicyService as jest.Mocked; + const mockedGetFullAgentPolicy = getFullAgentPolicy as jest.Mock< ReturnType >; @@ -144,6 +146,11 @@ describe('agent policy', () => { beforeEach(() => { soClient = getSavedObjectMock({ revision: 1, package_policies: ['package-1'] }); + mockedPackagePolicyService.findAllForAgentPolicy.mockReturnValue([ + { + id: 'package-1', + }, + ] as any); esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; (getAgentsByKuery as jest.Mock).mockResolvedValue({ @@ -153,10 +160,10 @@ describe('agent policy', () => { perPage: 10, }); - (packagePolicyService.delete as jest.Mock).mockResolvedValue([ + mockedPackagePolicyService.delete.mockResolvedValue([ { id: 'package-1', - }, + } as any, ]); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 4008100518984..9c70e4a58c388 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { uniq, omit, isEqual, keyBy } from 'lodash'; +import { omit, isEqual, keyBy } from 'lodash'; import uuidv5 from 'uuid/v5'; import { safeDump } from 'js-yaml'; import pMap from 'p-map'; @@ -186,8 +186,9 @@ class AgentPolicyService { } public hasAPMIntegration(agentPolicy: AgentPolicy) { - return agentPolicy.package_policies.some( - (p) => typeof p !== 'string' && p.package?.name === FLEET_APM_PACKAGE + return ( + agentPolicy.package_policies && + agentPolicy.package_policies.some((p) => p.package?.name === FLEET_APM_PACKAGE) ); } @@ -261,13 +262,7 @@ class AgentPolicyService { if (withPackagePolicies) { agentPolicy.package_policies = - (await packagePolicyService.getByIDs( - soClient, - (agentPolicySO.attributes.package_policies as string[]) || [], - { - ignoreMissing: true, - } - )) || []; + (await packagePolicyService.findAllForAgentPolicy(soClient, id)) || []; } return agentPolicy; @@ -458,7 +453,7 @@ class AgentPolicyService { ); // Copy all package policies and append (copy n) to their names - if (baseAgentPolicy.package_policies.length) { + if (baseAgentPolicy.package_policies) { const newPackagePolicies = await pMap( baseAgentPolicy.package_policies as PackagePolicy[], async (packagePolicy: PackagePolicy) => { @@ -610,75 +605,6 @@ class AgentPolicyService { return res; } - public async assignPackagePolicies( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - id: string, - packagePolicyIds: string[], - options: { user?: AuthenticatedUser; bumpRevision: boolean; force?: boolean } = { - bumpRevision: true, - } - ): Promise { - const oldAgentPolicy = await this.get(soClient, id, false); - - if (!oldAgentPolicy) { - throw new Error('Agent policy not found'); - } - - if (oldAgentPolicy.is_managed && !options?.force) { - throw new HostedAgentPolicyRestrictionRelatedError( - `Cannot update integrations of hosted agent policy ${id}` - ); - } - - return await this._update( - soClient, - esClient, - id, - { - package_policies: uniq( - [...((oldAgentPolicy.package_policies || []) as string[])].concat(packagePolicyIds) - ), - }, - options?.user, - { bumpRevision: options.bumpRevision } - ); - } - - public async unassignPackagePolicies( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - id: string, - packagePolicyIds: string[], - options?: { user?: AuthenticatedUser; force?: boolean } - ) { - const oldAgentPolicy = await this.get(soClient, id, false); - - if (!oldAgentPolicy) { - throw new Error('Agent policy not found'); - } - - if (oldAgentPolicy.is_managed && !options?.force) { - throw new HostedAgentPolicyRestrictionRelatedError( - `Cannot remove integrations of hosted agent policy ${id}` - ); - } - - return await this._update( - soClient, - esClient, - id, - { - package_policies: uniq( - [...((oldAgentPolicy.package_policies || []) as string[])].filter( - (packagePolicyId) => !packagePolicyIds.includes(packagePolicyId) - ) - ), - }, - options?.user - ); - } - public async delete( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -705,12 +631,14 @@ class AgentPolicyService { throw new Error('Cannot delete agent policy that is assigned to agent(s)'); } - if (agentPolicy.package_policies && agentPolicy.package_policies.length) { + const packagePolicies = await packagePolicyService.findAllForAgentPolicy(soClient, id); + + if (packagePolicies.length) { const deletedPackagePolicies: DeletePackagePoliciesResponse = await packagePolicyService.delete( soClient, esClient, - agentPolicy.package_policies as string[], + packagePolicies.map((p) => p.id), { force: options?.force, skipUnassignFromAgentPolicies: true, diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 0e9c2fda047d1..f7668f8fe19bf 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -55,6 +55,7 @@ import { PackagePolicyValidationError, PackagePolicyRestrictionRelatedError, PackagePolicyNotFoundError, + HostedAgentPolicyRestrictionRelatedError, } from '../errors'; import { NewPackagePolicySchema, PackagePolicySchema, UpdatePackagePolicySchema } from '../types'; import type { @@ -75,7 +76,7 @@ import { outputService } from './output'; import { getPackageInfo, getInstallation, ensureInstalledPackage } from './epm/packages'; import { getAssetsData } from './epm/packages/assets'; import { compileTemplate } from './epm/agent/agent'; -import { normalizeKuery } from './saved_object'; +import { escapeSearchQueryPhrase, normalizeKuery } from './saved_object'; import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; import type { PackageUpdateEvent, UpdateEventType } from './upgrade_sender'; @@ -120,6 +121,7 @@ class PackagePolicyService implements PackagePolicyServiceInterface { throw new IngestManagerError('You cannot add APM to a policy using a logstash output'); } } + await validateIsNotHostedPolicy(soClient, packagePolicy.policy_id, options?.force); // trailing whitespace causes issues creating API keys packagePolicy.name = packagePolicy.name.trim(); @@ -196,18 +198,11 @@ class PackagePolicyService implements PackagePolicyServiceInterface { { ...options, id: packagePolicyId } ); - // Assign it to the given agent policy - await agentPolicyService.assignPackagePolicies( - soClient, - esClient, - packagePolicy.policy_id, - [newSo.id], - { + if (options?.bumpRevision ?? true) { + await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, { user: options?.user, - bumpRevision: options?.bumpRevision ?? true, - force: options?.force, - } - ); + }); + } return { id: newSo.id, @@ -221,8 +216,9 @@ class PackagePolicyService implements PackagePolicyServiceInterface { esClient: ElasticsearchClient, packagePolicies: NewPackagePolicy[], agentPolicyId: string, - options?: { user?: AuthenticatedUser; bumpRevision?: boolean } + options?: { user?: AuthenticatedUser; bumpRevision?: boolean; force: true } ): Promise { + await validateIsNotHostedPolicy(soClient, agentPolicyId); const isoDate = new Date().toISOString(); // eslint-disable-next-line @typescript-eslint/naming-convention const { saved_objects } = await soClient.bulkCreate( @@ -254,16 +250,12 @@ class PackagePolicyService implements PackagePolicyServiceInterface { const newSos = saved_objects.filter((so) => !so.error && so.attributes); // Assign it to the given agent policy - await agentPolicyService.assignPackagePolicies( - soClient, - esClient, - agentPolicyId, - newSos.map((newSo) => newSo.id), - { + + if (options?.bumpRevision ?? true) { + await agentPolicyService.bumpRevision(soClient, esClient, agentPolicyId, { user: options?.user, - bumpRevision: options?.bumpRevision ?? true, - } - ); + }); + } return newSos.map((newSo) => ({ id: newSo.id, @@ -292,6 +284,26 @@ class PackagePolicyService implements PackagePolicyServiceInterface { }; } + public async findAllForAgentPolicy( + soClient: SavedObjectsClientContract, + agentPolicyId: string + ): Promise { + const packagePolicySO = await soClient.find({ + type: SAVED_OBJECT_TYPE, + filter: `${SAVED_OBJECT_TYPE}.attributes.policy_id:${escapeSearchQueryPhrase(agentPolicyId)}`, + perPage: SO_SEARCH_LIMIT, + }); + if (!packagePolicySO) { + return []; + } + + return packagePolicySO.saved_objects.map((so) => ({ + id: so.id, + version: so.version, + ...so.attributes, + })); + } + public async getByIDs( soClient: SavedObjectsClientContract, ids: string[], @@ -501,6 +513,13 @@ class PackagePolicyService implements PackagePolicyServiceInterface { throw new PackagePolicyRestrictionRelatedError(`Cannot delete package policy ${id}`); } + await validateIsNotHostedPolicy( + soClient, + packagePolicy?.policy_id, + options?.force, + 'Cannot remove integrations of hosted agent policy' + ); + const agentPolicy = await agentPolicyService .get(soClient, packagePolicy.policy_id) .catch((err) => { @@ -513,19 +532,12 @@ class PackagePolicyService implements PackagePolicyServiceInterface { throw err; }); + await soClient.delete(SAVED_OBJECT_TYPE, id); if (agentPolicy && !options?.skipUnassignFromAgentPolicies) { - await agentPolicyService.unassignPackagePolicies( - soClient, - esClient, - packagePolicy.policy_id, - [packagePolicy.id], - { - user: options?.user, - force: options?.force, - } - ); + await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, { + user: options?.user, + }); } - await soClient.delete(SAVED_OBJECT_TYPE, id); result.push({ id, name: packagePolicy.name, @@ -1281,6 +1293,11 @@ export interface PackagePolicyServiceInterface { get(soClient: SavedObjectsClientContract, id: string): Promise; + findAllForAgentPolicy( + soClient: SavedObjectsClientContract, + agentPolicyId: string + ): Promise; + getByIDs( soClient: SavedObjectsClientContract, ids: string[], @@ -1607,6 +1624,25 @@ export function preconfigurePackageInputs( return resultingPackagePolicy; } +async function validateIsNotHostedPolicy( + soClient: SavedObjectsClientContract, + id: string, + force = false, + errorMessage?: string +) { + const agentPolicy = await agentPolicyService.get(soClient, id, false); + + if (!agentPolicy) { + throw new Error('Agent policy not found'); + } + + if (agentPolicy.is_managed && !force) { + throw new HostedAgentPolicyRestrictionRelatedError( + errorMessage ?? `Cannot update integrations of hosted agent policy ${id}` + ); + } +} + function deepMergeVars(original: any, override: any, keepOriginalValue = false): any { if (!original.vars) { original.vars = { ...override.vars }; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index c0b24f927d848..245ab2316cb02 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -238,7 +238,8 @@ jest.mock('./epm/kibana/index_pattern/install'); jest.mock('./package_policy', () => ({ ...jest.requireActual('./package_policy'), packagePolicyService: { - getByIDs: jest.fn().mockReturnValue([]), + ...jest.requireActual('./package_policy').packagePolicyService, + findAllForAgentPolicy: jest.fn().mockReturnValue([]), listIds: jest.fn().mockReturnValue({ items: [] }), create: jest .fn() @@ -280,8 +281,8 @@ const spyAgentPolicyServicBumpAllAgentPoliciesForOutput = jest.spyOn( describe('policy preconfiguration', () => { beforeEach(() => { - mockedPackagePolicyService.getByIDs.mockReset(); mockedPackagePolicyService.create.mockReset(); + mockedPackagePolicyService.findAllForAgentPolicy.mockReset(); mockInstalledPackages.clear(); mockInstallPackageErrors.clear(); mockConfiguredPolicies.clear(); @@ -365,7 +366,7 @@ describe('policy preconfiguration', () => { it('should not add new package policy to existing non managed policies', async () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - mockedPackagePolicyService.getByIDs.mockResolvedValue([ + mockedPackagePolicyService.findAllForAgentPolicy.mockResolvedValue([ { name: 'test_package1' } as PackagePolicy, ]); @@ -415,7 +416,7 @@ describe('policy preconfiguration', () => { it('should add new package policy to existing managed policies', async () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - mockedPackagePolicyService.getByIDs.mockResolvedValue([ + mockedPackagePolicyService.findAllForAgentPolicy.mockResolvedValue([ { name: 'test_package1' } as PackagePolicy, ]); @@ -475,7 +476,7 @@ describe('policy preconfiguration', () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - mockedPackagePolicyService.getByIDs.mockResolvedValue([ + mockedPackagePolicyService.findAllForAgentPolicy.mockResolvedValue([ { name: 'Renamed package policy', id: 'test_package1' } as PackagePolicy, ]); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_agent_policy_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_agent_policy_generator.ts index 82294ac754fce..5b99a28e96ee0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_agent_policy_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_agent_policy_generator.ts @@ -22,7 +22,6 @@ export class FleetAgentPolicyGenerator extends BaseDataGenerator { - return agentPolicyGenerator.generate({ - package_policies: [packagePolicyId], + return agentPolicyGenerator.generate({}); + }), + perPage: Math.max(requiredPolicyIds.length, 10), + total: requiredPolicyIds.length, + page: 1, + }; + }, + }, + ]); + +export type FleetBulkGetAgentPolicyListHttpMockInterface = ResponseProvidersInterface<{ + agentPolicy: () => BulkGetAgentPoliciesResponse; +}>; +export const fleetBulkGetAgentPolicyListHttpMock = + httpHandlerMockFactory([ + { + id: 'agentPolicy', + path: AGENT_POLICY_API_ROUTES.BULK_GET_PATTERN, + method: 'post', + handler: ({ body }) => { + const generator = new EndpointDocGenerator('seed'); + const agentPolicyGenerator = new FleetAgentPolicyGenerator('seed'); + const endpointMetadata = generator.generateHostMetadata(); + const requiredPolicyIds: string[] = [ + // Make sure that the Agent policy returned from the API has the Integration Policy ID that + // the first endpoint metadata generated is using. This is needed especially when testing the + // Endpoint Details flyout where certain actions might be disabled if we know the endpoint integration policy no + // longer exists. + endpointMetadata.Endpoint.policy.applied.id, + + // In addition, some of our UI logic looks for the existence of certain Endpoint Integration policies + // using the Agents Policy API (normally when checking IDs since query by ids is not supported via API) + // so also add the first two package policy IDs that the `fleetGetEndpointPackagePolicyListHttpMock()` + // method above creates (which Trusted Apps HTTP mocks also use) + // FIXME: remove hard-coded IDs below and get them from the new FleetPackagePolicyGenerator (#2262) + 'ddf6570b-9175-4a6d-b288-61a09771c647', + 'b8e616ae-44fc-4be7-846c-ce8fa5c082dd', + ]; + + return { + items: requiredPolicyIds.map((packagePolicyId) => { + return agentPolicyGenerator.generate({}); + }), + }; + }, + }, + ]); + +export type FleetBulkGetPackagePoliciesListHttpMockInterface = ResponseProvidersInterface<{ + packagePolicies: () => BulkGetPackagePoliciesResponse; +}>; +export const fleetBulkGetPackagePoliciesListHttpMock = + httpHandlerMockFactory([ + { + id: 'packagePolicies', + path: PACKAGE_POLICY_API_ROUTES.BULK_GET_PATTERN, + method: 'post', + handler: ({ body }) => { + const generator = new EndpointDocGenerator('seed'); + const fleetPackagePolicyGenerator = new FleetPackagePolicyGenerator('seed'); + const endpointMetadata = generator.generateHostMetadata(); + const requiredPolicyIds: string[] = [ + // Make sure that the Agent policy returned from the API has the Integration Policy ID that + // the first endpoint metadata generated is using. This is needed especially when testing the + // Endpoint Details flyout where certain actions might be disabled if we know the endpoint integration policy no + // longer exists. + endpointMetadata.Endpoint.policy.applied.id, + + // In addition, some of our UI logic looks for the existence of certain Endpoint Integration policies + // using the Agents Policy API (normally when checking IDs since query by ids is not supported via API) + // so also add the first two package policy IDs that the `fleetGetEndpointPackagePolicyListHttpMock()` + // method above creates (which Trusted Apps HTTP mocks also use) + // FIXME: remove hard-coded IDs below and get them from the new FleetPackagePolicyGenerator (#2262) + 'ddf6570b-9175-4a6d-b288-61a09771c647', + 'b8e616ae-44fc-4be7-846c-ce8fa5c082dd', + + // And finally, include any kql filters for package policies ids + ...getPackagePoliciesFromKueryString( + `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies: (${( + JSON.parse(body?.toString() ?? '{}')?.ids as string[] + ).join(' or ')} )` + ), + ]; + + return { + items: requiredPolicyIds.map((packagePolicyId) => { + return fleetPackagePolicyGenerator.generate({ + id: packagePolicyId, + }); + }), + }; + }, + }, + ]); + +export type FleetGetPackagePoliciesListHttpMockInterface = ResponseProvidersInterface<{ + packagePolicies: () => GetPackagePoliciesResponse; +}>; +export const fleetGetPackagePoliciesListHttpMock = + httpHandlerMockFactory([ + { + id: 'packagePolicies', + path: PACKAGE_POLICY_API_ROUTES.LIST_PATTERN, + method: 'get', + handler: ({ query }) => { + const generator = new EndpointDocGenerator('seed'); + const fleetPackagePolicyGenerator = new FleetPackagePolicyGenerator('seed'); + const endpointMetadata = generator.generateHostMetadata(); + const requiredPolicyIds: string[] = [ + // Make sure that the Agent policy returned from the API has the Integration Policy ID that + // the first endpoint metadata generated is using. This is needed especially when testing the + // Endpoint Details flyout where certain actions might be disabled if we know the endpoint integration policy no + // longer exists. + endpointMetadata.Endpoint.policy.applied.id, + + // In addition, some of our UI logic looks for the existence of certain Endpoint Integration policies + // using the Agents Policy API (normally when checking IDs since query by ids is not supported via API) + // so also add the first two package policy IDs that the `fleetGetEndpointPackagePolicyListHttpMock()` + // method above creates (which Trusted Apps HTTP mocks also use) + // FIXME: remove hard-coded IDs below and get them from the new FleetPackagePolicyGenerator (#2262) + 'ddf6570b-9175-4a6d-b288-61a09771c647', + 'b8e616ae-44fc-4be7-846c-ce8fa5c082dd', + + // And finally, include any kql filters for package policies ids + ...getPackagePoliciesFromKueryString( + `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies: (${(query?.ids as string[]).join( + ' or ' + )} )` + ), + ]; + + return { + items: requiredPolicyIds.map((packagePolicyId) => { + return fleetPackagePolicyGenerator.generate({ + id: packagePolicyId, }); }), perPage: Math.max(requiredPolicyIds.length, 10), diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index 4cc923cf8d86b..7dfd55664acc7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -38,6 +38,9 @@ import { fleetGetAgentPolicyListHttpMock, fleetGetCheckPermissionsHttpMock, fleetGetPackageListHttpMock, + fleetBulkGetPackagePoliciesListHttpMock, + fleetBulkGetAgentPolicyListHttpMock, + fleetGetPackagePoliciesListHttpMock, } from '../../mocks'; type EndpointMetadataHttpMocksInterface = ResponseProvidersInterface<{ @@ -133,6 +136,9 @@ export const endpointListFleetApisHttpMock = composeHttpHandlerMocks([ fleetGetPackageListHttpMock, fleetGetAgentPolicyListHttpMock, + fleetBulkGetPackagePoliciesListHttpMock, + fleetBulkGetAgentPolicyListHttpMock, + fleetGetPackagePoliciesListHttpMock, fleetGetCheckPermissionsHttpMock, ]); type EndpointPageHttpMockInterface = EndpointMetadataHttpMocksInterface & diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts index 70b31f88b2f76..7d1fd0a3d77fe 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts @@ -50,6 +50,7 @@ import { jest.mock('../../../services/policies/ingest', () => ({ sendGetAgentConfigList: () => Promise.resolve({ items: [] }), sendGetAgentPolicyList: () => Promise.resolve({ items: [] }), + sendBulkGetPackagePolicies: () => Promise.resolve({ items: [] }), sendGetEndpointSecurityPackage: () => Promise.resolve({ version: '1.1.1' }), sendGetFleetAgentsWithEndpoint: () => Promise.resolve({ total: 0 }), })); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 2194e51ec5aaf..fb0e1949ad757 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -9,7 +9,6 @@ import type { DataViewBase, Query } from '@kbn/es-query'; import type { CoreStart, HttpStart } from '@kbn/core/public'; import type { Dispatch } from 'redux'; import semverGte from 'semver/functions/gte'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { BASE_POLICY_RESPONSE_ROUTE, HOST_METADATA_GET_ROUTE, @@ -41,7 +40,7 @@ import { createLoadingResourceState, } from '../../../state'; import { - sendGetAgentPolicyList, + sendBulkGetPackagePolicies, sendGetEndpointSecurityPackage, sendGetFleetAgentsWithEndpoint, } from '../../../services/policies/ingest'; @@ -173,19 +172,12 @@ const getAgentAndPoliciesForEndpointsList = async ( // Package Ids that it uses, thus if a reference exists there, then the package policy (policy) // exists. const policiesFound = ( - await sendGetAgentPolicyList(http, { - query: { - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies: (${policyIdsToCheck.join( - ' or ' - )})`, - }, - }) + await sendBulkGetPackagePolicies(http, policyIdsToCheck) ).items.reduce( - (list, agentPolicy) => { - (agentPolicy.package_policies as string[]).forEach((packagePolicy) => { - list.packagePolicy[packagePolicy as string] = true; - list.agentPolicy[packagePolicy as string] = agentPolicy.id; - }); + (list, packagePolicy) => { + list.packagePolicy[packagePolicy.id as string] = true; + list.agentPolicy[packagePolicy.id as string] = packagePolicy.policy_id; + return list; }, { packagePolicy: {}, agentPolicy: {} } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index 03a0fdda89797..5a983574f7545 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -11,6 +11,7 @@ import type { GetAgentPoliciesResponseItem, GetPackagesResponse, GetAgentsResponse, + BulkGetPackagePoliciesResponse, } from '@kbn/fleet-plugin/common/types/rest_spec'; import type { GetHostPolicyResponse, @@ -120,9 +121,6 @@ const endpointListApiPathHandlerMocks = ({ // Do policies referenced in endpoint list exist // just returns 1 single agent policy that includes all of the packagePolicy IDs provided [INGEST_API_AGENT_POLICIES]: (): GetAgentPoliciesResponse => { - (agentPolicy.package_policies as string[]).push( - ...endpointPackagePolicies.map((packagePolicy) => packagePolicy.id) - ); return { items: [agentPolicy], total: 10, @@ -146,6 +144,13 @@ const endpointListApiPathHandlerMocks = ({ }; }, + // List of Policies (package policies) for onboarding + [`${INGEST_API_PACKAGE_POLICIES}/_bulk_get`]: (): BulkGetPackagePoliciesResponse => { + return { + items: endpointPackagePolicies, + }; + }, + // List of Agents using Endpoint [INGEST_API_FLEET_AGENTS]: (): GetAgentsResponse => { return { @@ -209,4 +214,15 @@ export const setEndpointListApiMockImplementation: ( throw new Error(`MOCK: api request does not have a mocked handler: ${path}`); }); + + mockedHttpService.post.mockImplementation(async (...args) => { + const [path] = args; + if (typeof path === 'string') { + if (apiHandlers[path]) { + return apiHandlers[path](); + } + } + + throw new Error(`MOCK: api request does not have a mocked handler: ${path}`); + }); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 132b5684fa8c0..1d47f70227df3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -1066,9 +1066,11 @@ describe('when on the endpoint list page', () => { const packagePolicy = docGenerator.generatePolicyPackagePolicy(); packagePolicy.id = hosts[0].metadata.Endpoint.policy.applied.id; + const agentPolicy = generator.generateAgentPolicy(); agentPolicyId = agentPolicy.id; agentId = hosts[0].metadata.elastic.agent.id; + packagePolicy.policy_id = agentPolicyId; setEndpointListApiMockImplementation(coreStart.http, { endpointsResults: hostInfo, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx index 66137aab4cea1..5b74d99d6abcd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx @@ -12,7 +12,7 @@ import { createAppRootMockRenderer } from '../../../../common/mock/endpoint'; import { sendGetEndpointSpecificPackagePolicies } from '../../../services/policies/policies'; import { sendGetEndpointSpecificPackagePoliciesMock } from '../../../services/policies/test_mock_utils'; import { PolicyList } from './policy_list'; -import { sendGetAgentPolicyList } from '../../../services/policies/ingest'; +import { sendBulkGetAgentPolicyList } from '../../../services/policies/ingest'; import type { GetPolicyListResponse } from '../types'; import { getEndpointListPath, getPoliciesPath } from '../../../common/routing'; import { APP_UI_ID } from '../../../../../common/constants'; @@ -22,7 +22,7 @@ jest.mock('../../../services/policies/ingest'); const getPackagePolicies = sendGetEndpointSpecificPackagePolicies as jest.Mock; -const getAgentPolicies = sendGetAgentPolicyList as jest.Mock; +const mockedSendBulkGetAgentPolicies = sendBulkGetAgentPolicyList as jest.Mock; describe('When on the policy list page', () => { let render: () => ReturnType; @@ -77,19 +77,19 @@ describe('When on the policy list page', () => { beforeEach(async () => { getPackagePolicies.mockReturnValue(policies); - getAgentPolicies.mockReturnValue({ + mockedSendBulkGetAgentPolicies.mockReturnValue({ items: [ - { package_policies: [policies.items[0].id], agents: 4 }, - { package_policies: [policies.items[1].id], agents: 2 }, - { package_policies: [policies.items[2].id], agents: 5 }, - { package_policies: [policies.items[3].id], agents: 1 }, - { package_policies: [policies.items[4].id], agents: 3 }, + { package_policies: [{ id: policies.items[0].id }], agents: 4 }, + { package_policies: [{ id: policies.items[1].id }], agents: 2 }, + { package_policies: [{ id: policies.items[2].id }], agents: 5 }, + { package_policies: [{ id: policies.items[3].id }], agents: 1 }, + { package_policies: [{ id: policies.items[4].id }], agents: 3 }, ], }); render(); await waitFor(() => { expect(sendGetEndpointSpecificPackagePolicies).toHaveBeenCalled(); - expect(sendGetAgentPolicyList).toHaveBeenCalled(); + expect(sendBulkGetAgentPolicyList).toHaveBeenCalled(); }); }); it('should display the policy list table', () => { @@ -164,7 +164,7 @@ describe('When on the policy list page', () => { await waitFor(() => { expect(getPackagePolicies).toHaveBeenCalled(); expect(sendGetEndpointSpecificPackagePolicies).toHaveBeenCalled(); - expect(sendGetAgentPolicyList).toHaveBeenCalled(); + expect(mockedSendBulkGetAgentPolicies).toHaveBeenCalled(); }); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 43a7223260f28..33fd25d0d15cf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -19,7 +19,6 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { useLocation } from 'react-router-dom'; -import type { AgentPolicy } from '@kbn/fleet-plugin/common'; import type { CreatePackagePolicyRouteState } from '@kbn/fleet-plugin/public'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; import { AdministrationListPage } from '../../../components/administration_list_page'; @@ -58,10 +57,14 @@ export const PolicyList = memo(() => { // endpoint count per policy const policyIds = useMemo(() => data?.items.map((policies) => policies.id) ?? [], [data]); + const agentPolicyIds = useMemo( + () => data?.items.map((policies) => policies.policy_id) ?? [], + [data] + ); const { data: endpointCount = { items: [] } } = useGetAgentCountForPolicy({ - policyIds, + agentPolicyIds, customQueryOptions: { - enabled: policyIds.length > 0, + enabled: agentPolicyIds.length > 0, onError: (err) => { toasts.addDanger( i18n.translate('xpack.securitySolution.policyList.endpointCountError', { @@ -76,7 +79,7 @@ export const PolicyList = memo(() => { const { data: endpointPackageInfo, isFetching: packageIsFetching } = useGetEndpointSecurityPackage({ customQueryOptions: { - enabled: policyIds.length === 0, + enabled: agentPolicyIds.length === 0, onError: (err) => { toasts.addDanger( i18n.translate('xpack.securitySolution.policyList.packageVersionError', { @@ -88,11 +91,14 @@ export const PolicyList = memo(() => { }); const policyIdToEndpointCount = useMemo(() => { - const map = new Map(); - for (const policy of endpointCount?.items) { - for (const packagePolicyId of policy.package_policies) { - if (policyIds.includes(packagePolicyId as string)) { - map.set(packagePolicyId, policy.agents ?? 0); + const map = new Map(); + + for (const agentPolicy of endpointCount?.items) { + if (agentPolicy.package_policies) { + for (const packagePolicy of agentPolicy.package_policies) { + if (policyIds.includes(packagePolicy.id)) { + map.set(packagePolicy.id, agentPolicy.agents ?? 0); + } } } } diff --git a/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts b/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts index fef45b657d5da..3c0810b0d551b 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts @@ -8,10 +8,9 @@ import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query import { useQuery } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { GetAgentPoliciesResponse, GetPackagesResponse } from '@kbn/fleet-plugin/common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { useHttp } from '../../../common/lib/kibana'; import { MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../common/constants'; -import { sendGetAgentPolicyList, sendGetEndpointSecurityPackage } from './ingest'; +import { sendBulkGetAgentPolicyList, sendGetEndpointSecurityPackage } from './ingest'; import type { GetPolicyListResponse } from '../../pages/policy/types'; import { sendGetEndpointSpecificPackagePolicies } from './policies'; import type { ServerApiError } from '../../../common/types'; @@ -53,22 +52,17 @@ export function useGetEndpointSpecificPolicies( * This hook returns the fleet agent policies list filtered by policy id */ export function useGetAgentCountForPolicy({ - policyIds, + agentPolicyIds, customQueryOptions, }: { - policyIds: string[]; + agentPolicyIds: string[]; customQueryOptions?: UseQueryOptions; }): QueryObserverResult { const http = useHttp(); return useQuery( - ['endpointCountForPolicy', policyIds], + ['endpointCountForPolicy', agentPolicyIds], () => { - return sendGetAgentPolicyList(http, { - query: { - perPage: 50, - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies: (${policyIds.join(' or ')})`, - }, - }); + return sendBulkGetAgentPolicyList(http, agentPolicyIds); }, customQueryOptions ); diff --git a/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts b/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts index b77ab8a725ecf..7690348391ad7 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/ingest.ts @@ -12,6 +12,7 @@ import type { GetPackagesResponse, GetAgentPoliciesRequest, GetAgentPoliciesResponse, + GetPackagePoliciesResponse, } from '@kbn/fleet-plugin/common'; import type { NewPolicyData } from '../../../../common/endpoint/types'; import type { GetPolicyResponse, UpdatePolicyResponse } from '../../pages/policy/types'; @@ -37,6 +38,26 @@ export const sendGetPackagePolicy = ( return http.get(`${INGEST_API_PACKAGE_POLICIES}/${packagePolicyId}`, options); }; +/** + * Retrieves multiple package policies by ids + * @param http + * @param packagePolicyIds + * @param options + */ +export const sendBulkGetPackagePolicies = ( + http: HttpStart, + packagePolicyIds: string[], + options?: HttpFetchOptions +) => { + return http.post(`${INGEST_API_PACKAGE_POLICIES}/_bulk_get`, { + ...options, + body: JSON.stringify({ + ids: packagePolicyIds, + ignoreMissing: true, + }), + }); +}; + /** * Retrieve a list of Agent Policies * @param http @@ -49,6 +70,26 @@ export const sendGetAgentPolicyList = ( return http.get(INGEST_API_AGENT_POLICIES, options); }; +/** + * Retrieve a list of Agent Policies + * @param http + * @param options + */ +export const sendBulkGetAgentPolicyList = ( + http: HttpStart, + ids: string[], + options: HttpFetchOptions = {} +) => { + return http.post(`${INGEST_API_AGENT_POLICIES}/_bulk_get`, { + ...options, + body: JSON.stringify({ + ids, + ignoreMissing: true, + full: true, + }), + }); +}; + /** * Updates a package policy * 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 fc95c7a025393..d99b659439859 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 @@ -64,14 +64,7 @@ type AgentPolicyWithPackagePolicies = Omit & { const isAgentPolicyWithPackagePolicies = ( agentPolicy: AgentPolicy | AgentPolicyWithPackagePolicies ): agentPolicy is AgentPolicyWithPackagePolicies => { - if ( - agentPolicy.package_policies.length === 0 || - typeof agentPolicy.package_policies[0] !== 'string' - ) { - return true; - } - - return false; + return agentPolicy.package_policies ? true : false; }; export class EndpointMetadataService {