mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Fleet] Create task that periodically unenrolls inactive agents (#189861)
Closes https://github.com/elastic/kibana/issues/179399 ## Summary Create a new periodic task that unenrolls inactive agents based on `unenroll_timeout` set on agent policies In the agent policy settings there is now a new section:  ### Testing - Create a policy with `unenroll_timeout` set to any value - Enroll many agents to a policy and make them inactive - you can use Horde or the script in `fleet/scripts/create_agents' that can directly create inactive agents - Leave the local env running for at least 10 minutes - You should see logs that indicate that the task ran successfully and remove the inactive agents  Note that the executed unenroll action is also visible in the UI:  - If there are no agent policies with `unenroll_timeout` set or there are no inactive agents on those policies, you should see logs like these:  ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
0299a7a3bc
commit
15657536ed
12 changed files with 415 additions and 33 deletions
|
@ -22,7 +22,6 @@ import {
|
|||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBetaBadge,
|
||||
EuiBadge,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
|
@ -796,29 +795,14 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
|
|||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel"
|
||||
defaultMessage="Unenrollment timeout"
|
||||
defaultMessage="Inactive agent unenrollment timeout"
|
||||
/>
|
||||
|
||||
<EuiToolTip
|
||||
content={i18n.translate('xpack.fleet.agentPolicyForm.unenrollmentTimeoutTooltip', {
|
||||
defaultMessage:
|
||||
'This setting is deprecated and will be removed in a future release. Consider using inactivity timeout instead',
|
||||
})}
|
||||
>
|
||||
<EuiBetaBadge
|
||||
label={i18n.translate(
|
||||
'xpack.fleet.agentPolicyForm.unenrollmentTimeoutDeprecatedLabel',
|
||||
{ defaultMessage: 'Deprecated' }
|
||||
)}
|
||||
size="s"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription"
|
||||
defaultMessage="An optional timeout in seconds. If provided, and fleet server is below version 8.7.0, an agent will automatically unenroll after being gone for this period of time."
|
||||
defaultMessage="An optional timeout in seconds. If configured, inactive agents will be automatically unenrolled and their API keys will be invalidated after they've been inactive for this value in seconds. This can be useful for policies containing ephemeral agents, such as those in a Docker or Kubernetes environment."
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
|
|
@ -55,7 +55,7 @@ export const ActivityItem: React.FunctionComponent<{
|
|||
? action.nbAgentsAck
|
||||
: action.nbAgentsAck + ' of ' + action.nbAgentsActioned,
|
||||
agents: action.nbAgentsActioned === 1 ? 'agent' : 'agents',
|
||||
completedText: getAction(action.type).completedText,
|
||||
completedText: getAction(action.type, action.actionId).completedText,
|
||||
offlineText:
|
||||
action.status === 'ROLLOUT_PASSED' && action.nbAgentsActioned - action.nbAgentsAck > 0
|
||||
? `, ${
|
||||
|
@ -175,7 +175,7 @@ export const ActivityItem: React.FunctionComponent<{
|
|||
id="xpack.fleet.agentActivityFlyout.cancelledTitle"
|
||||
defaultMessage="Agent {cancelledText} cancelled"
|
||||
values={{
|
||||
cancelledText: getAction(action.type).cancelledText,
|
||||
cancelledText: getAction(action.type, action.actionId).cancelledText,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
|
@ -201,7 +201,7 @@ export const ActivityItem: React.FunctionComponent<{
|
|||
id="xpack.fleet.agentActivityFlyout.expiredTitle"
|
||||
defaultMessage="Agent {expiredText} expired"
|
||||
values={{
|
||||
expiredText: getAction(action.type).cancelledText,
|
||||
expiredText: getAction(action.type, action.actionId).cancelledText,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
|
|
|
@ -31,6 +31,11 @@ const actionNames: {
|
|||
completedText: 'force unenrolled',
|
||||
cancelledText: 'force unenrollment',
|
||||
},
|
||||
AUTOMATIC_FORCE_UNENROLL: {
|
||||
inProgressText: 'Automatic unenrolling',
|
||||
completedText: 'automatically unenrolled',
|
||||
cancelledText: 'automatic unenrollment',
|
||||
},
|
||||
UPDATE_TAGS: {
|
||||
inProgressText: 'Updating tags of',
|
||||
completedText: 'updated tags',
|
||||
|
@ -60,7 +65,13 @@ const actionNames: {
|
|||
ACTION: { inProgressText: 'Actioning', completedText: 'actioned', cancelledText: 'action' },
|
||||
};
|
||||
|
||||
export const getAction = (type?: string) => actionNames[type ?? 'ACTION'] ?? actionNames.ACTION;
|
||||
export const getAction = (type?: string, actionId?: string) => {
|
||||
// handling a special case of force unenrollment coming from an automatic task
|
||||
// we know what kind of action is from the actionId prefix
|
||||
if (actionId?.includes('UnenrollInactiveAgentsTask-'))
|
||||
return actionNames.AUTOMATICAL_FORCE_UNENROLL;
|
||||
return actionNames[type ?? 'ACTION'] ?? actionNames.ACTION;
|
||||
};
|
||||
|
||||
export const inProgressTitle = (action: ActionStatus) => (
|
||||
<FormattedMessage
|
||||
|
@ -74,7 +85,7 @@ export const inProgressTitle = (action: ActionStatus) => (
|
|||
? action.nbAgentsActioned
|
||||
: action.nbAgentsActioned - action.nbAgentsAck + ' of ' + action.nbAgentsActioned,
|
||||
agents: action.nbAgentsActioned === 1 ? 'agent' : 'agents',
|
||||
inProgressText: getAction(action.type).inProgressText,
|
||||
inProgressText: getAction(action.type, action.actionId).inProgressText,
|
||||
reassignText:
|
||||
action.type === 'POLICY_REASSIGN' && action.newPolicyId ? `to ${action.newPolicyId}` : '',
|
||||
upgradeText: action.type === 'UPGRADE' ? `to version ${action.version}` : '',
|
||||
|
|
|
@ -128,6 +128,7 @@ export const createAppContextStartContractMock = (
|
|||
},
|
||||
}
|
||||
: {}),
|
||||
unenrollInactiveAgentsTask: {} as any,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ import type { PackagePolicyService } from './services/package_policy_service';
|
|||
import { PackagePolicyServiceImpl } from './services/package_policy';
|
||||
import { registerFleetUsageLogger, startFleetUsageLogger } from './services/fleet_usage_logger';
|
||||
import { CheckDeletedFilesTask } from './tasks/check_deleted_files_task';
|
||||
import { UnenrollInactiveAgentsTask } from './tasks/unenroll_inactive_agents_task';
|
||||
import {
|
||||
UninstallTokenService,
|
||||
type UninstallTokenServiceInterface,
|
||||
|
@ -178,6 +179,7 @@ export interface FleetAppContext {
|
|||
messageSigningService: MessageSigningServiceInterface;
|
||||
auditLogger?: AuditLogger;
|
||||
uninstallTokenService: UninstallTokenServiceInterface;
|
||||
unenrollInactiveAgentsTask: UnenrollInactiveAgentsTask;
|
||||
}
|
||||
|
||||
export type FleetSetupContract = void;
|
||||
|
@ -266,6 +268,7 @@ export class FleetPlugin
|
|||
private fleetUsageSender?: FleetUsageSender;
|
||||
private checkDeletedFilesTask?: CheckDeletedFilesTask;
|
||||
private fleetMetricsTask?: FleetMetricsTask;
|
||||
private unenrollInactiveAgentsTask?: UnenrollInactiveAgentsTask;
|
||||
|
||||
private agentService?: AgentService;
|
||||
private packageService?: PackageService;
|
||||
|
@ -599,6 +602,11 @@ export class FleetPlugin
|
|||
taskManager: deps.taskManager,
|
||||
logFactory: this.initializerContext.logger,
|
||||
});
|
||||
this.unenrollInactiveAgentsTask = new UnenrollInactiveAgentsTask({
|
||||
core,
|
||||
taskManager: deps.taskManager,
|
||||
logFactory: this.initializerContext.logger,
|
||||
});
|
||||
|
||||
// Register fields metadata extractor
|
||||
registerIntegrationFieldsExtractor({ core, fieldsMetadata: deps.fieldsMetadata });
|
||||
|
@ -644,12 +652,14 @@ export class FleetPlugin
|
|||
bulkActionsResolver: this.bulkActionsResolver!,
|
||||
messageSigningService,
|
||||
uninstallTokenService,
|
||||
unenrollInactiveAgentsTask: this.unenrollInactiveAgentsTask!,
|
||||
});
|
||||
licenseService.start(plugins.licensing.license$);
|
||||
this.telemetryEventsSender.start(plugins.telemetry, core).catch(() => {});
|
||||
this.bulkActionsResolver?.start(plugins.taskManager).catch(() => {});
|
||||
this.fleetUsageSender?.start(plugins.taskManager).catch(() => {});
|
||||
this.checkDeletedFilesTask?.start({ taskManager: plugins.taskManager }).catch(() => {});
|
||||
this.unenrollInactiveAgentsTask?.start({ taskManager: plugins.taskManager }).catch(() => {});
|
||||
startFleetUsageLogger(plugins.taskManager).catch(() => {});
|
||||
this.fleetMetricsTask
|
||||
?.start(plugins.taskManager, core.elasticsearch.client.asInternalUser)
|
||||
|
|
|
@ -1245,10 +1245,6 @@ class AgentPolicyService {
|
|||
default_fleet_server: policy.is_default_fleet_server === true,
|
||||
};
|
||||
|
||||
if (policy.unenroll_timeout) {
|
||||
fleetServerPolicy.unenroll_timeout = policy.unenroll_timeout;
|
||||
}
|
||||
|
||||
acc.push(fleetServerPolicy);
|
||||
return acc;
|
||||
}, [] as FleetServerPolicy[]);
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 { coreMock } from '@kbn/core/server/mocks';
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
|
||||
import { TaskStatus } from '@kbn/task-manager-plugin/server';
|
||||
import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task';
|
||||
import type { CoreSetup } from '@kbn/core/server';
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
|
||||
import { agentPolicyService } from '../services';
|
||||
import { createAgentPolicyMock } from '../../common/mocks';
|
||||
import { createAppContextStartContractMock } from '../mocks';
|
||||
import { getAgentsByKuery } from '../services/agents';
|
||||
|
||||
import { appContextService } from '../services';
|
||||
|
||||
import { unenrollBatch } from '../services/agents/unenroll_action_runner';
|
||||
|
||||
import type { AgentPolicy } from '../types';
|
||||
|
||||
import { UnenrollInactiveAgentsTask, TYPE, VERSION } from './unenroll_inactive_agents_task';
|
||||
|
||||
jest.mock('../services');
|
||||
jest.mock('../services/agents');
|
||||
jest.mock('../services/agents/unenroll_action_runner');
|
||||
|
||||
const MOCK_TASK_INSTANCE = {
|
||||
id: `${TYPE}:${VERSION}`,
|
||||
runAt: new Date(),
|
||||
attempts: 0,
|
||||
ownerId: '',
|
||||
status: TaskStatus.Running,
|
||||
startedAt: new Date(),
|
||||
scheduledAt: new Date(),
|
||||
retryAt: new Date(),
|
||||
params: {},
|
||||
state: {},
|
||||
taskType: TYPE,
|
||||
};
|
||||
|
||||
const mockAgentPolicyService = agentPolicyService as jest.Mocked<typeof agentPolicyService>;
|
||||
const mockedGetAgentsByKuery = getAgentsByKuery as jest.MockedFunction<typeof getAgentsByKuery>;
|
||||
|
||||
describe('UnenrollInactiveAgentsTask', () => {
|
||||
const { createSetup: coreSetupMock } = coreMock;
|
||||
const { createSetup: tmSetupMock, createStart: tmStartMock } = taskManagerMock;
|
||||
|
||||
let mockContract: ReturnType<typeof createAppContextStartContractMock>;
|
||||
let mockTask: UnenrollInactiveAgentsTask;
|
||||
let mockCore: CoreSetup;
|
||||
let mockTaskManagerSetup: jest.Mocked<TaskManagerSetupContract>;
|
||||
const mockedUnenrollBatch = jest.mocked(unenrollBatch);
|
||||
|
||||
const agents = [
|
||||
{
|
||||
id: 'agent-1',
|
||||
policy_id: 'agent-policy-2',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
id: 'agent-2',
|
||||
policy_id: 'agent-policy-1',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
id: 'agent-3',
|
||||
policy_id: 'agent-policy-1',
|
||||
status: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
const getMockAgentPolicyFetchAllAgentPolicies = (items: AgentPolicy[]) =>
|
||||
jest.fn().mockResolvedValue(
|
||||
jest.fn(async function* () {
|
||||
yield items;
|
||||
})()
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
mockContract = createAppContextStartContractMock();
|
||||
appContextService.start(mockContract);
|
||||
mockCore = coreSetupMock();
|
||||
mockTaskManagerSetup = tmSetupMock();
|
||||
mockTask = new UnenrollInactiveAgentsTask({
|
||||
core: mockCore,
|
||||
taskManager: mockTaskManagerSetup,
|
||||
logFactory: loggingSystemMock.create(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Task lifecycle', () => {
|
||||
it('Should create task', () => {
|
||||
expect(mockTask).toBeInstanceOf(UnenrollInactiveAgentsTask);
|
||||
});
|
||||
|
||||
it('Should register task', () => {
|
||||
expect(mockTaskManagerSetup.registerTaskDefinitions).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should schedule task', async () => {
|
||||
const mockTaskManagerStart = tmStartMock();
|
||||
await mockTask.start({ taskManager: mockTaskManagerStart });
|
||||
expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task logic', () => {
|
||||
const runTask = async (taskInstance = MOCK_TASK_INSTANCE) => {
|
||||
const mockTaskManagerStart = tmStartMock();
|
||||
await mockTask.start({ taskManager: mockTaskManagerStart });
|
||||
const createTaskRunner =
|
||||
mockTaskManagerSetup.registerTaskDefinitions.mock.calls[0][0][TYPE].createTaskRunner;
|
||||
const taskRunner = createTaskRunner({ taskInstance });
|
||||
return taskRunner.run();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockAgentPolicyService.fetchAllAgentPolicies = getMockAgentPolicyFetchAllAgentPolicies([
|
||||
createAgentPolicyMock({ unenroll_timeout: 3000 }),
|
||||
createAgentPolicyMock({ id: 'agent-policy-2', unenroll_timeout: 1000 }),
|
||||
]);
|
||||
|
||||
mockedGetAgentsByKuery.mockResolvedValue({
|
||||
agents,
|
||||
} as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Should unenroll eligible agents', async () => {
|
||||
mockedUnenrollBatch.mockResolvedValueOnce({ actionId: 'actionid-01' });
|
||||
await runTask();
|
||||
expect(mockedUnenrollBatch).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
agents,
|
||||
{
|
||||
force: true,
|
||||
revoke: true,
|
||||
actionId: expect.stringContaining('UnenrollInactiveAgentsTask-'),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Should not run if task is outdated', async () => {
|
||||
const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' });
|
||||
|
||||
expect(mockedUnenrollBatch).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(getDeleteTaskRunResult());
|
||||
});
|
||||
|
||||
it('Should exit if there are no agents policies with unenroll_timeout set', async () => {
|
||||
mockAgentPolicyService.list.mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
});
|
||||
expect(mockedUnenrollBatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should exit if there are no eligible agents to unenroll', async () => {
|
||||
mockedGetAgentsByKuery.mockResolvedValue({
|
||||
agents: [],
|
||||
} as any);
|
||||
expect(mockedUnenrollBatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClient } from '@kbn/core/server';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type {
|
||||
CoreSetup,
|
||||
ElasticsearchClient,
|
||||
Logger,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/server';
|
||||
import type {
|
||||
ConcreteTaskInstance,
|
||||
TaskManagerSetupContract,
|
||||
TaskManagerStartContract,
|
||||
} from '@kbn/task-manager-plugin/server';
|
||||
import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task';
|
||||
import type { LoggerFactory } from '@kbn/core/server';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
|
||||
import { AGENTS_PREFIX, AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants';
|
||||
import { getAgentsByKuery } from '../services/agents';
|
||||
import { unenrollBatch } from '../services/agents/unenroll_action_runner';
|
||||
import { agentPolicyService, auditLoggingService } from '../services';
|
||||
|
||||
export const TYPE = 'fleet:unenroll-inactive-agents-task';
|
||||
export const VERSION = '1.0.0';
|
||||
const TITLE = 'Fleet Unenroll Inactive Agent Task';
|
||||
const SCOPE = ['fleet'];
|
||||
const INTERVAL = '10m';
|
||||
const TIMEOUT = '1m';
|
||||
const UNENROLLMENT_BATCHSIZE = 1000;
|
||||
const POLICIES_BATCHSIZE = 500;
|
||||
|
||||
interface UnenrollInactiveAgentsTaskSetupContract {
|
||||
core: CoreSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
logFactory: LoggerFactory;
|
||||
}
|
||||
|
||||
interface UnenrollInactiveAgentsTaskStartContract {
|
||||
taskManager: TaskManagerStartContract;
|
||||
}
|
||||
|
||||
export class UnenrollInactiveAgentsTask {
|
||||
private logger: Logger;
|
||||
private wasStarted: boolean = false;
|
||||
private abortController = new AbortController();
|
||||
|
||||
constructor(setupContract: UnenrollInactiveAgentsTaskSetupContract) {
|
||||
const { core, taskManager, logFactory } = setupContract;
|
||||
this.logger = logFactory.get(this.taskId);
|
||||
|
||||
taskManager.registerTaskDefinitions({
|
||||
[TYPE]: {
|
||||
title: TITLE,
|
||||
timeout: TIMEOUT,
|
||||
createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => {
|
||||
return {
|
||||
run: async () => {
|
||||
return this.runTask(taskInstance, core);
|
||||
},
|
||||
cancel: async () => {
|
||||
this.abortController.abort('Task timed out');
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start = async ({ taskManager }: UnenrollInactiveAgentsTaskStartContract) => {
|
||||
if (!taskManager) {
|
||||
this.logger.error('[UnenrollInactiveAgentsTask] Missing required service during start');
|
||||
return;
|
||||
}
|
||||
|
||||
this.wasStarted = true;
|
||||
this.logger.info(`[UnenrollInactiveAgentsTask] Started with interval of [${INTERVAL}]`);
|
||||
|
||||
try {
|
||||
await taskManager.ensureScheduled({
|
||||
id: this.taskId,
|
||||
taskType: TYPE,
|
||||
scope: SCOPE,
|
||||
schedule: {
|
||||
interval: INTERVAL,
|
||||
},
|
||||
state: {},
|
||||
params: { version: VERSION },
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.error(`Error scheduling task UnenrollInactiveAgentsTask, error: ${e.message}`, e);
|
||||
}
|
||||
};
|
||||
|
||||
private get taskId(): string {
|
||||
return `${TYPE}:${VERSION}`;
|
||||
}
|
||||
|
||||
private endRun(msg: string = '') {
|
||||
this.logger.info(`[UnenrollInactiveAgentsTask] runTask ended${msg ? ': ' + msg : ''}`);
|
||||
}
|
||||
|
||||
public async unenrollInactiveAgents(
|
||||
esClient: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract
|
||||
) {
|
||||
this.logger.debug(
|
||||
`[UnenrollInactiveAgentsTask] Fetching agent policies with unenroll_timeout > 0`
|
||||
);
|
||||
// find all agent policies that are not managed and having unenroll_timeout > 0
|
||||
// limit the search to POLICIES_BATCHSIZE at a time and loop until there are no agent policies left
|
||||
const policiesKuery = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.is_managed: false AND ${AGENT_POLICY_SAVED_OBJECT_TYPE}.unenroll_timeout > 0`;
|
||||
let agentCounter = 0;
|
||||
|
||||
const agentPolicyFetcher = await agentPolicyService.fetchAllAgentPolicies(soClient, {
|
||||
kuery: policiesKuery,
|
||||
perPage: POLICIES_BATCHSIZE,
|
||||
});
|
||||
for await (const agentPolicyPageResults of agentPolicyFetcher) {
|
||||
this.logger.debug(
|
||||
`[UnenrollInactiveAgentsTask] Found "${agentPolicyPageResults.length}" agent policies with unenroll_timeout > 0`
|
||||
);
|
||||
if (!agentPolicyPageResults.length) {
|
||||
this.endRun('Found no policies to process');
|
||||
return;
|
||||
}
|
||||
|
||||
// find inactive agents enrolled on above policies
|
||||
// limit batch size to UNENROLLMENT_BATCHSIZE to avoid scale issues
|
||||
const kuery = `(${AGENTS_PREFIX}.policy_id:${agentPolicyPageResults
|
||||
.map((policy) => `"${policy.id}"`)
|
||||
.join(' or ')}) and ${AGENTS_PREFIX}.status: inactive`;
|
||||
const res = await getAgentsByKuery(esClient, soClient, {
|
||||
kuery,
|
||||
showInactive: true,
|
||||
page: 1,
|
||||
perPage: UNENROLLMENT_BATCHSIZE,
|
||||
});
|
||||
if (!res.agents.length) {
|
||||
this.endRun('No inactive agents to unenroll');
|
||||
return;
|
||||
}
|
||||
agentCounter += res.agents.length;
|
||||
if (agentCounter >= UNENROLLMENT_BATCHSIZE) {
|
||||
this.endRun('Reached the maximum amount of agents to unenroll, exiting.');
|
||||
return;
|
||||
}
|
||||
this.logger.debug(
|
||||
`[UnenrollInactiveAgentsTask] Found "${res.agents.length}" inactive agents to unenroll. Attempting unenrollment`
|
||||
);
|
||||
const unenrolledBatch = await unenrollBatch(soClient, esClient, res.agents, {
|
||||
revoke: true,
|
||||
force: true,
|
||||
actionId: `UnenrollInactiveAgentsTask-${uuidv4()}`,
|
||||
});
|
||||
auditLoggingService.writeCustomAuditLog({
|
||||
message: `Recurrent unenrollment of ${agentCounter} inactive agents due to unenroll_timeout option set on agent policy. Fleet action [id=${unenrolledBatch.actionId}]`,
|
||||
});
|
||||
this.logger.debug(
|
||||
`[UnenrollInactiveAgentsTask] Executed unenrollment of ${agentCounter} inactive agents with actionId: ${unenrolledBatch.actionId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public runTask = async (taskInstance: ConcreteTaskInstance, core: CoreSetup) => {
|
||||
if (!this.wasStarted) {
|
||||
this.logger.debug('[UnenrollInactiveAgentsTask] runTask Aborted. Task not started yet');
|
||||
return;
|
||||
}
|
||||
// Check that this task is current
|
||||
if (taskInstance.id !== this.taskId) {
|
||||
this.logger.debug(
|
||||
`[UnenrollInactiveAgentsTask] Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]`
|
||||
);
|
||||
return getDeleteTaskRunResult();
|
||||
}
|
||||
|
||||
this.logger.info(`[runTask()] started`);
|
||||
|
||||
const [coreStart] = await core.getStartServices();
|
||||
const esClient = coreStart.elasticsearch.client.asInternalUser;
|
||||
const soClient = new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
||||
|
||||
try {
|
||||
await this.unenrollInactiveAgents(esClient, soClient);
|
||||
|
||||
this.endRun('success');
|
||||
} catch (err) {
|
||||
if (err instanceof errors.RequestAbortedError) {
|
||||
this.logger.warn(`[UnenrollInactiveAgentsTask] request aborted due to timeout: ${err}`);
|
||||
this.endRun();
|
||||
return;
|
||||
}
|
||||
this.logger.error(`[UnenrollInactiveAgentsTask] error: ${err}`);
|
||||
this.endRun('error');
|
||||
}
|
||||
};
|
||||
}
|
|
@ -19627,10 +19627,8 @@
|
|||
"xpack.fleet.agentPolicyForm.tamperingSwitchLabel": "Empêcher la falsification des agents",
|
||||
"xpack.fleet.agentPolicyForm.tamperingSwitchLabel.disabledWarning": "L'intégration Elastic Defend est nécessaire pour activer cette fonctionnalité",
|
||||
"xpack.fleet.agentPolicyForm.tamperingUninstallLink": "Obtenir la commande de désinstallation",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDeprecatedLabel": "Déclassé",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription": "Délai d'expiration facultatif en secondes. Si une valeur est indiquée et que la version du serveur Fleet est inférieure à 8.7.0, un agent est automatiquement désenregistré après une période d'inactivité équivalente à ce délai.",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel": "Délai d'expiration pour le désenregistrement",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutTooltip": "Ce paramètre est déclassé et sera retiré dans une prochaine version. Envisagez d'utiliser le délai d'inactivité à la place",
|
||||
"xpack.fleet.agentPolicyForm.unenrollTimeoutMinValueErrorMessage": "Le délai de désenregistrement doit être supérieur à zéro.",
|
||||
"xpack.fleet.agentPolicyList.actionsColumnTitle": "Actions",
|
||||
"xpack.fleet.agentPolicyList.addButton": "Créer une stratégie d'agent",
|
||||
|
|
|
@ -19615,10 +19615,8 @@
|
|||
"xpack.fleet.agentPolicyForm.tamperingSwitchLabel": "エージェントの改ざんを防止",
|
||||
"xpack.fleet.agentPolicyForm.tamperingSwitchLabel.disabledWarning": "この機能を有効にするには、Elastic Defend統合が必要です。",
|
||||
"xpack.fleet.agentPolicyForm.tamperingUninstallLink": "アンインストールコマンドを取得",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDeprecatedLabel": "非推奨",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription": "任意のタイムアウト(秒)。指定され、Fleetサーバーのバージョンが8.7.0より前の場合、この期間が経過した後、エージェントは自動的に登録解除されます。",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel": "登録解除タイムアウト",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutTooltip": "この設定はサポートが終了し、今後のリリースでは削除されます。代わりに、非アクティブタイムアウトの使用を検討してください。",
|
||||
"xpack.fleet.agentPolicyForm.unenrollTimeoutMinValueErrorMessage": "登録解除タイムアウトは0よりも大きい値でなければなりません。",
|
||||
"xpack.fleet.agentPolicyList.actionsColumnTitle": "アクション",
|
||||
"xpack.fleet.agentPolicyList.addButton": "エージェントポリシーを作成",
|
||||
|
|
|
@ -19641,10 +19641,8 @@
|
|||
"xpack.fleet.agentPolicyForm.tamperingSwitchLabel": "防止篡改代理",
|
||||
"xpack.fleet.agentPolicyForm.tamperingSwitchLabel.disabledWarning": "需要 Elastic Defend 集成才能启用此功能",
|
||||
"xpack.fleet.agentPolicyForm.tamperingUninstallLink": "获取卸载命令",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDeprecatedLabel": "(已过时)",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription": "可选超时(秒)。若提供,且 Fleet 服务器的版本低于 8.7.0,代理断开连接此段时间后,将自动注销。",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel": "注销超时",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutTooltip": "此设置已过时,将在未来版本中移除。考虑改用非活动超时",
|
||||
"xpack.fleet.agentPolicyForm.unenrollTimeoutMinValueErrorMessage": "取消注册超时必须大于零。",
|
||||
"xpack.fleet.agentPolicyList.actionsColumnTitle": "操作",
|
||||
"xpack.fleet.agentPolicyList.addButton": "创建代理策略",
|
||||
|
|
|
@ -140,6 +140,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'fleet:check-deleted-files-task',
|
||||
'fleet:reassign_action:retry',
|
||||
'fleet:request_diagnostics:retry',
|
||||
'fleet:unenroll-inactive-agents-task',
|
||||
'fleet:unenroll_action:retry',
|
||||
'fleet:update_agent_tags:retry',
|
||||
'fleet:upgrade_action:retry',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue