diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 5bd4895969afe..69859855d74f0 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -781,7 +781,8 @@ export async function addPackageToAgentPolicy( defaultOutput: Output, packagePolicyName?: string, packagePolicyDescription?: string, - transformPackagePolicy?: (p: NewPackagePolicy) => NewPackagePolicy + transformPackagePolicy?: (p: NewPackagePolicy) => NewPackagePolicy, + bumpAgentPolicyRevison = false ) { const packageInfo = await getPackageInfo({ savedObjectsClient: soClient, @@ -803,7 +804,7 @@ export async function addPackageToAgentPolicy( : basePackagePolicy; await packagePolicyService.create(soClient, esClient, newPackagePolicy, { - bumpRevision: false, + bumpRevision: bumpAgentPolicyRevison, skipEnsureInstalled: true, skipUniqueNameVerification: true, overwrite: true, diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 50f741aa13567..4b87c0957c961 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -12,6 +12,7 @@ import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import type { InstallResult, + PackagePolicy, PreconfiguredAgentPolicy, PreconfiguredOutput, } from '../../common/types'; @@ -28,11 +29,13 @@ import { cleanPreconfiguredOutputs, } from './preconfiguration'; import { outputService } from './output'; +import { packagePolicyService } from './package_policy'; jest.mock('./agent_policy_update'); jest.mock('./output'); const mockedOutputService = outputService as jest.Mocked; +const mockedPackagePolicyService = packagePolicyService as jest.Mocked; const mockInstalledPackages = new Map(); const mockInstallPackageErrors = new Map(); @@ -165,13 +168,15 @@ jest.mock('./package_policy', () => ({ packagePolicyService: { getByIDs: jest.fn().mockReturnValue([]), listIds: jest.fn().mockReturnValue({ items: [] }), - create(soClient: any, esClient: any, newPackagePolicy: NewPackagePolicy) { - return { - id: 'mocked', - version: 'mocked', - ...newPackagePolicy, - }; - }, + create: jest + .fn() + .mockImplementation((soClient: any, esClient: any, newPackagePolicy: NewPackagePolicy) => { + return { + id: 'mocked', + version: 'mocked', + ...newPackagePolicy, + }; + }), get(soClient: any, id: string) { return { id: 'mocked', @@ -203,6 +208,7 @@ const spyAgentPolicyServicBumpAllAgentPoliciesForOutput = jest.spyOn( describe('policy preconfiguration', () => { beforeEach(() => { + mockedPackagePolicyService.create.mockReset(); mockInstalledPackages.clear(); mockInstallPackageErrors.clear(); mockConfiguredPolicies.clear(); @@ -274,6 +280,111 @@ describe('policy preconfiguration', () => { expect(nonFatalErrors.length).toBe(0); }); + it('should not add new package policy to existing non managed policies', async () => { + const soClient = getPutPreconfiguredPackagesMock(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + mockedPackagePolicyService.getByIDs.mockResolvedValue([ + { name: 'test_package1' } as PackagePolicy, + ]); + + mockConfiguredPolicies.set('test-id', { + name: 'Test policy', + description: 'Test policy description', + unenroll_timeout: 120, + namespace: 'default', + id: 'test-id', + package_policies: [ + { + name: 'test_package1', + }, + ], + } as PreconfiguredAgentPolicy); + + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [ + { + name: 'Test policy', + namespace: 'default', + id: 'test-id', + is_managed: false, + package_policies: [ + { + package: { name: 'test_package' }, + name: 'test_package1', + }, + { + package: { name: 'test_package' }, + name: 'test_package2', + }, + ], + }, + ] as PreconfiguredAgentPolicy[], + [{ name: 'test_package', version: '3.0.0' }], + mockDefaultOutput + ); + + expect(mockedPackagePolicyService.create).not.toBeCalled(); + }); + + it('should add new package policy to existing managed policies', async () => { + const soClient = getPutPreconfiguredPackagesMock(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + mockedPackagePolicyService.getByIDs.mockResolvedValue([ + { name: 'test_package1' } as PackagePolicy, + ]); + + mockConfiguredPolicies.set('test-id', { + name: 'Test policy', + description: 'Test policy description', + unenroll_timeout: 120, + namespace: 'default', + id: 'test-id', + package_policies: [ + { + name: 'test_package1', + }, + ], + is_managed: true, + } as PreconfiguredAgentPolicy); + + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [ + { + name: 'Test policy', + namespace: 'default', + id: 'test-id', + is_managed: true, + package_policies: [ + { + package: { name: 'test_package' }, + name: 'test_package1', + }, + { + package: { name: 'test_package' }, + name: 'test_package2', + }, + ], + }, + ] as PreconfiguredAgentPolicy[], + [{ name: 'test_package', version: '3.0.0' }], + mockDefaultOutput + ); + + expect(mockedPackagePolicyService.create).toBeCalledTimes(1); + expect(mockedPackagePolicyService.create).toBeCalledWith( + expect.anything(), // so client + expect.anything(), // es client + expect.objectContaining({ + name: 'test_package2', + }), + expect.anything() // options + ); + }); + it('should throw an error when trying to install duplicate packages', async () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; @@ -354,6 +465,7 @@ describe('policy preconfiguration', () => { '[Test policy] could not be added. [test_package] is not installed, add [test_package] to [xpack.fleet.packages] or remove it from [Test package].' ); }); + it('should not attempt to recreate or modify an agent policy if its ID is unchanged', async () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index effd594131a9c..8b906b68556a4 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -21,11 +21,7 @@ import type { PreconfiguredOutput, PackagePolicy, } from '../../common'; -import { - AGENT_POLICY_SAVED_OBJECT_TYPE, - SO_SEARCH_LIMIT, - normalizeHostsForAgents, -} from '../../common'; +import { SO_SEARCH_LIMIT, normalizeHostsForAgents } from '../../common'; import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, PRECONFIGURATION_LATEST_KEYWORD, @@ -344,7 +340,8 @@ export async function ensurePreconfiguredPackagesAndPolicies( esClient, policy!, packagePoliciesToAdd!, - defaultOutput + defaultOutput, + !created ); // Add the is_managed flag after configuring package policies to avoid errors @@ -412,7 +409,8 @@ async function addPreconfiguredPolicyPackages( inputs?: InputsOverride[]; } >, - defaultOutput: Output + defaultOutput: Output, + bumpAgentPolicyRevison = false ) { // Add packages synchronously to avoid overwriting for (const { installedPackage, name, description, inputs } of installedPackagePolicies) { @@ -430,7 +428,8 @@ async function addPreconfiguredPolicyPackages( defaultOutput, name, description, - (policy) => overridePackageInputs(policy, packageInfo, inputs) + (policy) => overridePackageInputs(policy, packageInfo, inputs), + bumpAgentPolicyRevison ); } }