From f083d2da2eec714f2ac5d967202a40e7206e2b4b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 8 Nov 2023 07:42:11 -0500 Subject: [PATCH] [8.11] [Fleet] Modify bulk unenroll to include inactive agents (#170249) (#170825) # Backport This will backport the following commits from `main` to `8.11`: - [[Fleet] Modify bulk unenroll to include inactive agents (#170249)](https://github.com/elastic/kibana/pull/170249) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Cristina Amico --- .../plugins/fleet/common/openapi/bundled.json | 20 +++-- .../plugins/fleet/common/openapi/bundled.yaml | 17 +++-- .../openapi/paths/agents@bulk_unenroll.yaml | 15 ++-- .../fleet/common/types/rest_spec/agent.ts | 1 + .../components/agent_unenroll_modal/index.tsx | 2 + .../server/routes/agent/unenroll_handler.ts | 1 + .../fleet/server/services/agents/unenroll.ts | 1 + .../fleet/server/types/rest_spec/agent.ts | 1 + .../apis/agents/unenroll.ts | 75 ++++++++++++++++++- 9 files changed, 112 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index a4604a7d7427b..77f0bcae3ed0a 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2740,14 +2740,6 @@ "schema": { "type": "object", "properties": { - "revoke": { - "type": "boolean", - "description": "Revokes API keys of agents" - }, - "force": { - "type": "boolean", - "description": "Unenroll hosted agents too" - }, "agents": { "oneOf": [ { @@ -2762,6 +2754,18 @@ "description": "list of agent IDs" } ] + }, + "revoke": { + "type": "boolean", + "description": "Revokes API keys of agents" + }, + "force": { + "type": "boolean", + "description": "Unenrolls hosted agents too" + }, + "includeInactive": { + "type": "boolean", + "description": "When passing agents by KQL query, unenrolls inactive agents too" } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index be132c9f19e48..bb80c1d16723f 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1719,12 +1719,6 @@ paths: schema: type: object properties: - revoke: - type: boolean - description: Revokes API keys of agents - force: - type: boolean - description: Unenroll hosted agents too agents: oneOf: - type: string @@ -1733,6 +1727,17 @@ paths: items: type: string description: list of agent IDs + revoke: + type: boolean + description: Revokes API keys of agents + force: + type: boolean + description: Unenrolls hosted agents too + includeInactive: + type: boolean + description: >- + When passing agents by KQL query, unenrolls inactive agents + too required: - agents example: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml index 1ab9e4038b978..a765e4868442b 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml @@ -23,12 +23,6 @@ post: schema: type: object properties: - revoke: - type: boolean - description: Revokes API keys of agents - force: - type: boolean - description: Unenroll hosted agents too agents: oneOf: - type: string @@ -37,6 +31,15 @@ post: items: type: string description: list of agent IDs + revoke: + type: boolean + description: Revokes API keys of agents + force: + type: boolean + description: Unenrolls hosted agents too + includeInactive: + type: boolean + description: When passing agents by KQL query, unenrolls inactive agents too required: - agents example: diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 1e0e82d6c2ecd..edb3f7f0eefcb 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -85,6 +85,7 @@ export interface PostBulkAgentUnenrollRequest { agents: string[] | string; force?: boolean; revoke?: boolean; + includeInactive?: boolean; }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx index 6fac687107fd1..6d32a8afeb769 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx @@ -47,6 +47,8 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ : await sendPostBulkAgentUnenroll({ agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, revoke: forceUnenroll, + // includeInactive is only used when the agents are selected by query, it's ignored in the case of agent ids + includeInactive: true, }); if (error) { throw error; diff --git a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts index 740cf3a7b716c..ec72f23a9876d 100644 --- a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts @@ -55,6 +55,7 @@ export const postBulkAgentsUnenrollHandler: RequestHandler< revoke: request.body?.revoke, force: request.body?.force, batchSize: request.body?.batchSize, + showInactive: request.body?.includeInactive, }); return response.ok({ body: { actionId: results.actionId } }); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index f13a8f91d81e9..e2c2a17c4d2ad 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -73,6 +73,7 @@ export async function unenrollAgents( force?: boolean; revoke?: boolean; batchSize?: number; + showInactive?: boolean; } ): Promise<{ actionId: string }> { if ('agentIds' in options) { diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index e90fda99cdff9..902380fc28ca1 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -95,6 +95,7 @@ export const PostBulkAgentUnenrollRequestSchema = { force: schema.maybe(schema.boolean()), revoke: schema.maybe(schema.boolean()), batchSize: schema.maybe(schema.number()), + includeInactive: schema.maybe(schema.boolean()), }), }; diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index e3153ad422ffc..120bd026ec3f1 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; +import { AGENTS_INDEX } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { setupFleetAndAgents } from './services'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -149,7 +150,7 @@ export default function (providerContext: FtrProviderContext) { expect(actionStatus.nbAgentsFailed).to.eql(2); }); - it('/agents/bulk_unenroll should allow to unenroll multiple agents by id from an regular agent policy', async () => { + it('/agents/bulk_unenroll should allow to unenroll multiple agents by id from a regular agent policy', async () => { // set policy to regular await supertest .put(`/api/fleet/agent_policies/policy1`) @@ -188,6 +189,78 @@ export default function (providerContext: FtrProviderContext) { expect(body.total).to.eql(0); }); + it('/agents/bulk_unenroll should allow to unenroll active and inactive agents by kuery with includeInactive', async () => { + // Agent inactive + await esClient.update({ + id: 'agent4', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + policy_id: 'policy1', + policy_revision_idx: 1, + last_checkin: new Date(Date.now() - 1000 * 60).toISOString(), // policy timeout 1 min + }, + }, + }); + // unenroll all agents that had last checkin before "now" + await supertest + .post(`/api/fleet/agents/bulk_unenroll`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: `last_checkin<="${new Date(Date.now()).toISOString()}"`, + revoke: true, + includeInactive: true, + }) + .expect(200); + + const { body } = await supertest.get(`/api/fleet/agents`); + expect(body.total).to.eql(0); + }); + it('/agents/bulk_unenroll should allow to unenroll inactive agents that never had last checkin by kuery with includeInactive', async () => { + // Agent inactive + await esClient.update({ + id: 'agent4', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + policy_id: 'policy1', + policy_revision_idx: 1, + last_checkin: new Date(Date.now() - 1000 * 60).toISOString(), // policy timeout 1 min + }, + }, + }); + // agent inactive through enrolled_at as no last_checkin + await esClient.create({ + id: 'agent5', + refresh: 'wait_for', + index: AGENTS_INDEX, + document: { + active: true, + access_api_key_id: 'api-key-4', + policy_id: 'policy1', + type: 'PERMANENT', + local_metadata: { host: { hostname: 'host6' } }, + user_provided_metadata: {}, + enrolled_at: new Date(Date.now() - 1000 * 60).toISOString(), // policy timeout 1 min + }, + }); + // unenroll all agents + await supertest + .post(`/api/fleet/agents/bulk_unenroll`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: 'active: true', + revoke: true, + includeInactive: true, + }) + .expect(200); + + const { body } = await supertest.get(`/api/fleet/agents`); + expect(body.total).to.eql(0); + }); + it('/agents/bulk_unenroll should allow to unenroll multiple agents by kuery in batches async', async () => { const { body } = await supertest .post(`/api/fleet/agents/bulk_unenroll`)