diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index ede984a3ea39c..8b49ca1b32329 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -237,6 +237,7 @@ class AppContextService { // soClient as kibana internal users, be careful on how you use it, security is not enabled return appContextService.getSavedObjects().getScopedClient(fakeRequest, { excludedExtensions: [SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID], + includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE], }); } diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 0a44a3df0f294..1f58fcafd396b 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -46,7 +46,7 @@ import { appContextService } from '../../app_context'; import { agentPolicyService, getAgentPolicySavedObjectType } from '../../agent_policy'; import { isSpaceAwarenessEnabled } from '../../spaces/helpers'; -interface UninstallTokenSOAttributes { +export interface UninstallTokenSOAttributes { policy_id: string; token: string; token_plain: string; diff --git a/x-pack/plugins/fleet/server/services/spaces/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/spaces/agent_policy.test.ts index ab69d4708c436..079148a6ae041 100644 --- a/x-pack/plugins/fleet/server/services/spaces/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/spaces/agent_policy.test.ts @@ -39,6 +39,22 @@ describe('updateAgentPolicySpaces', () => { jest .mocked(appContextService.getInternalUserSOClientWithoutSpaceExtension()) .updateObjectsSpaces.mockResolvedValue({ objects: [] }); + + jest + .mocked(appContextService.getInternalUserSOClientWithoutSpaceExtension()) + .find.mockResolvedValue({ + total: 1, + page: 1, + per_page: 100, + saved_objects: [ + { + id: 'token1', + attributes: { + namespaces: ['default'], + }, + } as any, + ], + }); }); it('does nothings if agent policy already in correct space', async () => { @@ -87,6 +103,18 @@ describe('updateAgentPolicySpaces', () => { ['default'], { namespace: 'default', refresh: 'wait_for' } ); + + expect( + jest.mocked(appContextService.getInternalUserSOClientWithoutSpaceExtension()).bulkUpdate + ).toBeCalledWith([ + { + id: 'token1', + type: 'fleet-uninstall-tokens', + attributes: { + namespaces: ['test'], + }, + }, + ]); }); it('throw when trying to change space to a policy with reusable package policies', async () => { diff --git a/x-pack/plugins/fleet/server/services/spaces/agent_policy.ts b/x-pack/plugins/fleet/server/services/spaces/agent_policy.ts index 2f8d5ff1b14c7..e123ca4426654 100644 --- a/x-pack/plugins/fleet/server/services/spaces/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/spaces/agent_policy.ts @@ -13,6 +13,8 @@ import { AGENTS_INDEX, AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, + SO_SEARCH_LIMIT, + UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, } from '../../../common/constants'; import { appContextService } from '../app_context'; @@ -22,6 +24,7 @@ import { packagePolicyService } from '../package_policy'; import { FleetError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { isSpaceAwarenessEnabled } from './helpers'; +import type { UninstallTokenSOAttributes } from '../security/uninstall_token_service'; export async function updateAgentPolicySpaces({ agentPolicyId, @@ -112,6 +115,25 @@ export async function updateAgentPolicySpaces({ } } + // Update uninstall tokens + const uninstallTokensRes = await soClient.find({ + perPage: SO_SEARCH_LIMIT, + type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, + filter: `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.policy_id:"${agentPolicyId}"`, + }); + + if (uninstallTokensRes.total > 0) { + await soClient.bulkUpdate( + uninstallTokensRes.saved_objects.map((so) => ({ + id: so.id, + type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, + attributes: { + namespaces: newSpaceIds, + }, + })) + ); + } + // Update fleet server index agents, enrollment api keys await esClient.updateByQuery({ index: ENROLLMENT_API_KEYS_INDEX, diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/change_space_agent_policies.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/change_space_agent_policies.ts index 4d1d08ac7b35c..df3aa6f8f257b 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/change_space_agent_policies.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/change_space_agent_policies.ts @@ -109,18 +109,29 @@ export default function (providerContext: FtrProviderContext) { }) .catch(() => {}); }); - async function assertPolicyAvailableInSpace(spaceId?: string) { - await apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, spaceId); + + async function assertPackagePolicyAvailableInSpace(spaceId?: string) { await apiClient.getPackagePolicy(defaultPackagePolicy1.item.id, spaceId); + } + + async function assertPackagePolicyNotAvailableInSpace(spaceId?: string) { + await expectToRejectWithNotFound(() => + apiClient.getPackagePolicy(defaultPackagePolicy1.item.id, spaceId) + ); + } + + async function assertAgentPolicyAvailableInSpace(policyId: string, spaceId?: string) { + await apiClient.getAgentPolicy(policyId, spaceId); const enrollmentApiKeys = await apiClient.getEnrollmentApiKeys(spaceId); - expect( - enrollmentApiKeys.items.find((item) => item.policy_id === defaultSpacePolicy1.item.id) - ).not.to.be(undefined); + expect(enrollmentApiKeys.items.find((item) => item.policy_id === policyId)).not.to.be( + undefined + ); const agents = await apiClient.getAgents(spaceId); - expect( - agents.items.filter((a) => a.policy_id === defaultSpacePolicy1.item.id).length - ).to.be(1); + expect(agents.items.filter((a) => a.policy_id === policyId).length).to.be(1); + + const uninstallTokens = await apiClient.getUninstallTokens(spaceId); + expect(uninstallTokens.items.filter((t) => t.policy_id === policyId).length).to.be(1); } async function assertEnrollemntApiKeysForSpace(spaceId?: string, policyIds?: string[]) { @@ -136,23 +147,19 @@ export default function (providerContext: FtrProviderContext) { expect([...foundPolicyIds].sort()).to.eql(policyIds?.sort()); } - async function assertPolicyNotAvailableInSpace(spaceId?: string) { - await expectToRejectWithNotFound(() => - apiClient.getPackagePolicy(defaultPackagePolicy1.item.id, spaceId) - ); - await expectToRejectWithNotFound(() => - apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, spaceId) - ); + async function assertAgentPolicyNotAvailableInSpace(policyId: string, spaceId?: string) { + await expectToRejectWithNotFound(() => apiClient.getAgentPolicy(policyId, spaceId)); const enrollmentApiKeys = await apiClient.getEnrollmentApiKeys(spaceId); - expect( - enrollmentApiKeys.items.find((item) => item.policy_id === defaultSpacePolicy1.item.id) - ).to.be(undefined); + expect(enrollmentApiKeys.items.find((item) => item.policy_id === policyId)).to.be( + undefined + ); const agents = await apiClient.getAgents(spaceId); - expect( - agents.items.filter((a) => a.policy_id === defaultSpacePolicy1.item.id).length - ).to.be(0); + expect(agents.items.filter((a) => a.policy_id === policyId).length).to.be(0); + + const uninstallTokens = await apiClient.getUninstallTokens(spaceId); + expect(uninstallTokens.items.filter((t) => t.policy_id === policyId).length).to.be(0); } async function assertAgentSpaces(agentId: string, expectedSpaces: string[]) { @@ -173,8 +180,11 @@ export default function (providerContext: FtrProviderContext) { space_ids: ['default', TEST_SPACE_1], }); - await assertPolicyAvailableInSpace(); - await assertPolicyAvailableInSpace(TEST_SPACE_1); + await assertAgentPolicyAvailableInSpace(defaultSpacePolicy1.item.id); + await assertAgentPolicyAvailableInSpace(defaultSpacePolicy1.item.id, TEST_SPACE_1); + + await assertPackagePolicyAvailableInSpace(); + await assertPackagePolicyAvailableInSpace(TEST_SPACE_1); await assertAgentSpaces(policy1AgentId, ['default', TEST_SPACE_1]); await assertAgentSpaces(policy2AgentId, ['default']); @@ -184,6 +194,9 @@ export default function (providerContext: FtrProviderContext) { defaultSpacePolicy2.item.id, ]); await assertEnrollemntApiKeysForSpace(TEST_SPACE_1, [defaultSpacePolicy1.item.id]); + // Ensure no side effect on other policies + await assertAgentPolicyAvailableInSpace(defaultSpacePolicy2.item.id); + await assertAgentPolicyNotAvailableInSpace(defaultSpacePolicy2.item.id, TEST_SPACE_1); }); it('should allow set policy in test space only', async () => { @@ -194,12 +207,17 @@ export default function (providerContext: FtrProviderContext) { space_ids: [TEST_SPACE_1], }); - await assertPolicyNotAvailableInSpace(); - await assertPolicyAvailableInSpace(TEST_SPACE_1); + await assertAgentPolicyNotAvailableInSpace(defaultSpacePolicy1.item.id); + await assertAgentPolicyAvailableInSpace(defaultSpacePolicy1.item.id, TEST_SPACE_1); + await assertPackagePolicyAvailableInSpace(TEST_SPACE_1); + await assertPackagePolicyNotAvailableInSpace(); await assertAgentSpaces(policy1AgentId, [TEST_SPACE_1]); await assertAgentSpaces(policy2AgentId, ['default']); await assertEnrollemntApiKeysForSpace('default', [defaultSpacePolicy2.item.id]); await assertEnrollemntApiKeysForSpace(TEST_SPACE_1, [defaultSpacePolicy1.item.id]); + // Ensure no side effect on other policies + await assertAgentPolicyAvailableInSpace(defaultSpacePolicy2.item.id); + await assertAgentPolicyNotAvailableInSpace(defaultSpacePolicy2.item.id, TEST_SPACE_1); }); it('should not allow add policy to a space where user do not have access', async () => {