From fdd5e0be75483868a096484ee261e5717124af6c Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet <nicolas.chaulet@elastic.co>
Date: Wed, 30 Oct 2024 10:16:54 -0400
Subject: [PATCH] [Fleet] Fix update query when change agent policy spaces
 (#198175)

---
 .../server/services/spaces/agent_policy.ts    | 18 ++++++
 .../change_space_agent_policies.ts            | 61 +++++++++++++++++--
 .../apis/space_awareness/helpers.ts           | 10 +++
 3 files changed, 84 insertions(+), 5 deletions(-)

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 14d7f45f2c47c..2f8d5ff1b14c7 100644
--- a/x-pack/plugins/fleet/server/services/spaces/agent_policy.ts
+++ b/x-pack/plugins/fleet/server/services/spaces/agent_policy.ts
@@ -115,12 +115,30 @@ export async function updateAgentPolicySpaces({
   // Update fleet server index agents, enrollment api keys
   await esClient.updateByQuery({
     index: ENROLLMENT_API_KEYS_INDEX,
+    query: {
+      bool: {
+        must: {
+          terms: {
+            policy_id: [agentPolicyId],
+          },
+        },
+      },
+    },
     script: `ctx._source.namespaces = [${newSpaceIds.map((spaceId) => `"${spaceId}"`).join(',')}]`,
     ignore_unavailable: true,
     refresh: true,
   });
   await esClient.updateByQuery({
     index: AGENTS_INDEX,
+    query: {
+      bool: {
+        must: {
+          terms: {
+            policy_id: [agentPolicyId],
+          },
+        },
+      },
+    },
     script: `ctx._source.namespaces = [${newSpaceIds.map((spaceId) => `"${spaceId}"`).join(',')}]`,
     ignore_unavailable: true,
     refresh: true,
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 7a803fd4f66db..4d1d08ac7b35c 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
@@ -15,6 +15,7 @@ import {
   createFleetAgent,
   expectToRejectWithError,
   expectToRejectWithNotFound,
+  getFleetAgentDoc,
 } from './helpers';
 import { testUsers, setupTestUsers } from '../test_users';
 
@@ -32,8 +33,12 @@ export default function (providerContext: FtrProviderContext) {
     const apiClient = new SpaceTestApiClient(supertest);
 
     let defaultSpacePolicy1: CreateAgentPolicyResponse;
+    let defaultSpacePolicy2: CreateAgentPolicyResponse;
     let defaultPackagePolicy1: GetOnePackagePolicyResponse;
 
+    let policy1AgentId: string;
+    let policy2AgentId: string;
+
     before(async () => {
       TEST_SPACE_1 = spaces.getDefaultTestSpace();
       await setupTestUsers(getService('security'), true);
@@ -44,14 +49,20 @@ export default function (providerContext: FtrProviderContext) {
       await cleanFleetIndices(esClient);
 
       await apiClient.postEnableSpaceAwareness();
-      const _policyRes = await apiClient.createAgentPolicy();
-      defaultSpacePolicy1 = _policyRes;
+      const [_policyRes1, _policyRes2] = await Promise.all([
+        apiClient.createAgentPolicy(),
+        apiClient.createAgentPolicy(),
+      ]);
+      defaultSpacePolicy1 = _policyRes1;
+      defaultSpacePolicy2 = _policyRes2;
       await apiClient.installPackage({
         pkgName: 'nginx',
         pkgVersion: '1.20.0',
         force: true, // To avoid package verification
       });
-      await createFleetAgent(esClient, defaultSpacePolicy1.item.id);
+      policy1AgentId = await createFleetAgent(esClient, defaultSpacePolicy1.item.id);
+      policy2AgentId = await createFleetAgent(esClient, defaultSpacePolicy2.item.id);
+
       const packagePolicyRes = await apiClient.createPackagePolicy(undefined, {
         policy_ids: [defaultSpacePolicy1.item.id],
         name: `test-nginx-${Date.now()}`,
@@ -107,7 +118,22 @@ export default function (providerContext: FtrProviderContext) {
         ).not.to.be(undefined);
 
         const agents = await apiClient.getAgents(spaceId);
-        expect(agents.total).to.be(1);
+        expect(
+          agents.items.filter((a) => a.policy_id === defaultSpacePolicy1.item.id).length
+        ).to.be(1);
+      }
+
+      async function assertEnrollemntApiKeysForSpace(spaceId?: string, policyIds?: string[]) {
+        const spaceApiKeys = await apiClient.getEnrollmentApiKeys(spaceId);
+
+        const foundPolicyIds = spaceApiKeys.items.reduce((acc, apiKey) => {
+          if (apiKey.policy_id) {
+            acc.add(apiKey.policy_id);
+          }
+          return acc;
+        }, new Set<string>());
+
+        expect([...foundPolicyIds].sort()).to.eql(policyIds?.sort());
       }
 
       async function assertPolicyNotAvailableInSpace(spaceId?: string) {
@@ -124,7 +150,19 @@ export default function (providerContext: FtrProviderContext) {
         ).to.be(undefined);
 
         const agents = await apiClient.getAgents(spaceId);
-        expect(agents.total).to.be(0);
+        expect(
+          agents.items.filter((a) => a.policy_id === defaultSpacePolicy1.item.id).length
+        ).to.be(0);
+      }
+
+      async function assertAgentSpaces(agentId: string, expectedSpaces: string[]) {
+        const agentDoc = await getFleetAgentDoc(esClient, agentId);
+
+        if (expectedSpaces.length === 1 && expectedSpaces[0] === 'default') {
+          expect(agentDoc._source?.namespaces ?? ['default']).to.eql(expectedSpaces);
+        } else {
+          expect(agentDoc._source?.namespaces).to.eql(expectedSpaces);
+        }
       }
 
       it('should allow set policy in multiple space', async () => {
@@ -137,6 +175,15 @@ export default function (providerContext: FtrProviderContext) {
 
         await assertPolicyAvailableInSpace();
         await assertPolicyAvailableInSpace(TEST_SPACE_1);
+
+        await assertAgentSpaces(policy1AgentId, ['default', TEST_SPACE_1]);
+        await assertAgentSpaces(policy2AgentId, ['default']);
+
+        await assertEnrollemntApiKeysForSpace('default', [
+          defaultSpacePolicy1.item.id,
+          defaultSpacePolicy2.item.id,
+        ]);
+        await assertEnrollemntApiKeysForSpace(TEST_SPACE_1, [defaultSpacePolicy1.item.id]);
       });
 
       it('should allow set policy in test space only', async () => {
@@ -149,6 +196,10 @@ export default function (providerContext: FtrProviderContext) {
 
         await assertPolicyNotAvailableInSpace();
         await assertPolicyAvailableInSpace(TEST_SPACE_1);
+        await assertAgentSpaces(policy1AgentId, [TEST_SPACE_1]);
+        await assertAgentSpaces(policy2AgentId, ['default']);
+        await assertEnrollemntApiKeysForSpace('default', [defaultSpacePolicy2.item.id]);
+        await assertEnrollemntApiKeysForSpace(TEST_SPACE_1, [defaultSpacePolicy1.item.id]);
       });
 
       it('should not allow add policy to a space where user do not have access', async () => {
diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts
index a757bdfdeae65..92f4e3a387678 100644
--- a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts
+++ b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts
@@ -13,6 +13,7 @@ import {
   AGENT_ACTIONS_RESULTS_INDEX,
   AGENT_POLICY_INDEX,
   AGENTS_INDEX,
+  type FleetServerAgent,
 } from '@kbn/fleet-plugin/common';
 import { ENROLLMENT_API_KEYS_INDEX } from '@kbn/fleet-plugin/common/constants';
 import { asyncForEach } from '@kbn/std';
@@ -117,6 +118,15 @@ export async function createFleetAgent(esClient: Client, agentPolicyId: string,
   return agentResponse._id;
 }
 
+export async function getFleetAgentDoc(esClient: Client, agentId: string) {
+  const agentResponse = await esClient.get<FleetServerAgent>({
+    index: '.fleet-agents',
+    id: agentId,
+  });
+
+  return agentResponse;
+}
+
 export async function makeAgentsUpgradeable(esClient: Client, agentIds: string[], version: string) {
   await asyncForEach(agentIds, async (agentId) => {
     await esClient.update({