diff --git a/x-pack/platform/plugins/shared/fleet/server/services/package_policy.ts b/x-pack/platform/plugins/shared/fleet/server/services/package_policy.ts index 6f030f79de5a3..31896bd6ca88a 100644 --- a/x-pack/platform/plugins/shared/fleet/server/services/package_policy.ts +++ b/x-pack/platform/plugins/shared/fleet/server/services/package_policy.ts @@ -1320,10 +1320,8 @@ class PackagePolicyClientImpl implements PackagePolicyClient { for (const policyId of packagePolicyUpdate.policy_ids) { const agentPolicy = await agentPolicyService.get(soClient, policyId, true); - if ((agentPolicy?.space_ids?.length ?? 0) > 1) { - throw new FleetError( - 'Reusable integration policies cannot be used with agent policies belonging to multiple spaces.' - ); + if (agentPolicy) { + validateReusableIntegrationsAndSpaceAwareness(packagePolicy, [agentPolicy]); } // Validate that if supports_agentless is true, the package actually supports agentless @@ -1635,6 +1633,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { internalSoClientWithoutSpaceExtension, packagePolicy.policy_ids.map((policyId) => ({ id: policyId, spaceId: '*' })) ); + + validateReusableIntegrationsAndSpaceAwareness(packagePolicy, agentPolicies); + if (!agentPolicies.some((policy) => policy.is_managed)) { logger.debug( `Saving previous revision of package policy ${id} with package version ${oldPackagePolicy.version}` diff --git a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/api_helper.ts index ff0afda601f13..c98a231dc08b0 100644 --- a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/api_helper.ts +++ b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -17,6 +17,7 @@ import type { GetOneAgentResponse, GetOnePackagePolicyResponse, GetPackagePoliciesResponse, + UpdatePackagePolicyResponse, } from '@kbn/fleet-plugin/common'; import type { GetEnrollmentAPIKeysResponse, @@ -99,6 +100,29 @@ export class SpaceTestApiClient { ): Promise { const { body: res, statusCode } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies`) + .auth(this.auth.username, this.auth.password) + .set('kbn-xsrf', 'xxxx') + .send(data); + + if (statusCode === 200) { + return res; + } + + if (statusCode === 404) { + throw new Error('404 "Not Found"'); + } else { + throw new Error(`${statusCode} "${res?.error}" ${res.message}`); + } + } + + async updatePackagePolicy( + packagePolicyId: string, + data: Partial = {}, + spaceId?: string + ): Promise { + const { body: res, statusCode } = await this.supertest + .put(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies/${packagePolicyId}`) + .auth(this.auth.username, this.auth.password) .set('kbn-xsrf', 'xxxx') .send(data); @@ -112,6 +136,25 @@ export class SpaceTestApiClient { throw new Error(`${statusCode} "${res?.error}" ${res.message}`); } } + + async deletePackagePolicy(packagePolicyId: string, spaceId?: string) { + const { body: res, statusCode } = await this.supertest + .delete(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies/${packagePolicyId}`) + .auth(this.auth.username, this.auth.password) + .set('kbn-xsrf', 'xxxx') + .send(); + + if (statusCode === 200) { + return res; + } + + if (statusCode === 404) { + throw new Error('404 "Not Found"'); + } else { + throw new Error(`${statusCode} "${res?.error}" ${res.message}`); + } + } + async getPackagePolicy( packagePolicyId: string, spaceId?: string @@ -450,6 +493,7 @@ export class SpaceTestApiClient { ) { const { body: res } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/epm/packages/${pkgName}/${pkgVersion}`) + .auth(this.auth.username, this.auth.password) .set('kbn-xsrf', 'xxxx') .send({ force }) .expect(200); diff --git a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/helpers.ts b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/helpers.ts index 175c8855b0a9e..eaa580efa8874 100644 --- a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/helpers.ts +++ b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/helpers.ts @@ -42,18 +42,21 @@ export async function cleanFleetIndices(esClient: Client) { q: '*', ignore_unavailable: true, refresh: true, + conflicts: 'proceed', }), esClient.deleteByQuery({ index: AGENTS_INDEX, q: '*', ignore_unavailable: true, refresh: true, + conflicts: 'proceed', }), esClient.deleteByQuery({ index: AGENT_ACTIONS_INDEX, q: '*', ignore_unavailable: true, refresh: true, + conflicts: 'proceed', }), ]); } @@ -64,6 +67,7 @@ export async function cleanFleetAgents(esClient: Client) { q: '*', ignore_unavailable: true, refresh: true, + conflicts: 'proceed', }); } @@ -73,6 +77,7 @@ export async function cleanFleetAgentPolicies(esClient: Client) { q: '*', refresh: true, ignore_unavailable: true, + conflicts: 'proceed', }); } @@ -84,12 +89,14 @@ export async function cleanFleetActionIndices(esClient: Client) { q: '*', ignore_unavailable: true, refresh: true, + conflicts: 'proceed', }), esClient.deleteByQuery( { index: AGENT_ACTIONS_RESULTS_INDEX, q: '*', refresh: true, + conflicts: 'proceed', }, ES_INDEX_OPTIONS ), diff --git a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/index.js b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/index.js index 70d05c8034dcd..0280db2c548e5 100644 --- a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/index.js +++ b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/index.js @@ -20,5 +20,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./space_awareness_migration')); loadTestFile(require.resolve('./telemetry')); loadTestFile(require.resolve('./outputs')); + loadTestFile(require.resolve('./package_policies')); }); } diff --git a/x-pack/platform/test/fleet_api_integration/apis/space_awareness/package_policies.ts b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/package_policies.ts new file mode 100644 index 0000000000000..e4496eab35b97 --- /dev/null +++ b/x-pack/platform/test/fleet_api_integration/apis/space_awareness/package_policies.ts @@ -0,0 +1,158 @@ +/* + * 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 expect from '@kbn/expect'; +import type { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import type { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices, expectToRejectWithError } from './helpers'; +import { setupTestUsers, testUsers } from '../test_users'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + const spaces = getService('spaces'); + let TEST_SPACE_1: string; + + describe('package policies', function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertestWithoutAuth, { + username: testUsers.fleet_all_int_all.username, + password: testUsers.fleet_all_int_all.password, + }); + + let multiSpacePolicy: CreateAgentPolicyResponse; + let defaultSpacePolicy: CreateAgentPolicyResponse; + + before(async () => { + await setupTestUsers(getService('security'), true); + TEST_SPACE_1 = spaces.getDefaultTestSpace(); + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + + await apiClient.postEnableSpaceAwareness(); + + await spaces.createTestSpace(TEST_SPACE_1); + multiSpacePolicy = await apiClient.createAgentPolicy(undefined, { + space_ids: ['default', TEST_SPACE_1], + }); + defaultSpacePolicy = await apiClient.createAgentPolicy(undefined, { + space_ids: ['default'], + }); + await apiClient.installPackage({ pkgName: 'nginx', force: true, pkgVersion: '1.20.0' }); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + describe('POST /package_policies', () => { + let packagePolicyId: string; + after(async () => { + if (packagePolicyId) { + await apiClient.deletePackagePolicy(packagePolicyId); + } + }); + it('should allow to add a package policy to a multi-space agent policy in the default space', async () => { + const packagePolicyRes = await apiClient.createPackagePolicy(undefined, { + policy_ids: [multiSpacePolicy.item.id], + name: `test-nginx-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }); + + expect(packagePolicyRes.item).to.have.property('id'); + packagePolicyId = packagePolicyRes.item.id; + }); + + it('should not allow to add a reusable package policy to a multi-space agent policy in the default space', async () => { + await expectToRejectWithError( + () => + apiClient.createPackagePolicy(undefined, { + policy_ids: [multiSpacePolicy.item.id, defaultSpacePolicy.item.id], + name: `test-nginx-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }), + /400 "Bad Request" Reusable integration policies cannot be used with agent policies belonging to multiple spaces./ + ); + }); + }); + + describe('PUT /package_policies', () => { + let packagePolicyId: string; + before(async () => { + const packagePolicyRes = await apiClient.createPackagePolicy(undefined, { + policy_ids: [multiSpacePolicy.item.id], + name: `test-nginx-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }); + packagePolicyId = packagePolicyRes.item.id; + }); + after(async () => { + if (packagePolicyId) { + await apiClient.deletePackagePolicy(packagePolicyId); + } + }); + it('should allow to edit a package policy in a multi-space agent policy in the default space', async () => { + const packagePolicyRes = await apiClient.updatePackagePolicy(packagePolicyId, { + policy_ids: [multiSpacePolicy.item.id], + name: `test-nginx-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }); + + expect(packagePolicyRes.item).to.have.property('id'); + packagePolicyId = packagePolicyRes.item.id; + }); + + it('should not allow to make a policy used in multiple space reusable', async () => { + await expectToRejectWithError( + () => + apiClient.updatePackagePolicy(packagePolicyId, { + policy_ids: [multiSpacePolicy.item.id, defaultSpacePolicy.item.id], + name: `test-nginx-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }), + /400 "Bad Request" Reusable integration policies cannot be used with agent policies belonging to multiple spaces./ + ); + }); + }); + }); +}