[Fleet] Fix agentless policy deletion unenroll agents (#216513)

This commit is contained in:
Nicolas Chaulet 2025-04-01 10:40:13 -04:00 committed by GitHub
parent e7edff9ddc
commit f578f401d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 4 deletions

View file

@ -0,0 +1,54 @@
/*
* 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 { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { agentPolicyUpdateEventHandler } from './agent_policy_update';
import { createAppContextStartContractMock } from '../mocks';
import { appContextService } from './app_context';
import { getAgentById, getAgentPolicyForAgent, getAgentsByKuery } from './agents';
jest.mock('./agents/crud', () => ({
...jest.requireActual('./agents/crud'),
getAgentsByKuery: jest.fn(),
getAgentById: jest.fn(),
getAgentPolicyForAgent: jest.fn(),
}));
jest.mock('./api_keys');
describe('agentPolicyUpdateEventHandler', () => {
describe('deleted', () => {
it('should unenroll agentless agents', async () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();
appContextService.start(createAppContextStartContractMock());
jest
.mocked(getAgentsByKuery)
.mockResolvedValueOnce({
agents: [{ id: 'agent1' }],
} as any)
.mockResolvedValueOnce({
agents: [],
} as any);
jest.mocked(getAgentById).mockResolvedValue({
id: 'agent1',
} as any);
jest.mocked(getAgentPolicyForAgent).mockResolvedValue({
supports_agentless: true,
} as any);
await agentPolicyUpdateEventHandler(esClient, 'deleted', 'test1');
expect(esClient.update).toBeCalledWith(
expect.objectContaining({
id: 'agent1',
doc: expect.objectContaining({
unenrollment_started_at: expect.anything(),
}),
})
);
});
});
});

View file

@ -30,7 +30,10 @@ import {
async function unenrollAgentIsAllowed(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string
agentId: string,
options?: {
skipAgentlessValidation?: boolean;
}
) {
const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId);
if (agentPolicy?.is_managed) {
@ -39,7 +42,7 @@ async function unenrollAgentIsAllowed(
);
}
if (agentPolicy?.supports_agentless) {
if (!options?.skipAgentlessValidation && agentPolicy?.supports_agentless) {
throw new FleetError(`Cannot unenroll agentless agent ${agentId}`);
}
@ -52,12 +55,15 @@ export async function unenrollAgent(
agentId: string,
options?: {
force?: boolean;
skipAgentlessValidation?: boolean;
revoke?: boolean;
}
) {
await getAgentById(esClient, soClient, agentId); // throw 404 if agent not in namespace
if (!options?.force) {
await unenrollAgentIsAllowed(soClient, esClient, agentId);
await unenrollAgentIsAllowed(soClient, esClient, agentId, {
skipAgentlessValidation: options?.skipAgentlessValidation,
});
}
if (options?.revoke) {
return forceUnenrollAgent(esClient, soClient, agentId);

View file

@ -31,7 +31,9 @@ export async function unenrollForAgentPolicyId(
hasMore = false;
}
for (const agent of agents) {
await unenrollAgent(soClient, esClient, agent.id);
await unenrollAgent(soClient, esClient, agent.id, {
skipAgentlessValidation: true,
});
}
}
}