[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:

![Screenshot 2024-08-06 at 12 31
37](https://github.com/user-attachments/assets/f66164c5-3eff-442d-91bc-367387cefe3d)



### 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
![Screenshot 2024-08-06 at 12 14
13](https://github.com/user-attachments/assets/573f32fb-eedb-4bee-918c-f26fedec9e0b)
Note that the executed unenroll action is also visible in the UI:
![Screenshot 2024-08-06 at 12 19
52](https://github.com/user-attachments/assets/942932ac-70dd-4d77-bf47-20007ac54748)
- 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:
![Screenshot 2024-08-06 at 12 13
49](https://github.com/user-attachments/assets/8868c228-fd09-4ecf-ad02-e07a94812638)





### 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:
Cristina Amico 2024-08-19 15:09:47 +02:00 committed by GitHub
parent 0299a7a3bc
commit 15657536ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 415 additions and 33 deletions

View file

@ -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"
/>
&nbsp;
<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."
/>
}
>

View file

@ -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>

View file

@ -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}` : '',

View file

@ -128,6 +128,7 @@ export const createAppContextStartContractMock = (
},
}
: {}),
unenrollInactiveAgentsTask: {} as any,
};
};

View file

@ -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)

View file

@ -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[]);

View file

@ -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();
});
});
});

View file

@ -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');
}
};
}

View file

@ -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",

View file

@ -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": "エージェントポリシーを作成",

View file

@ -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": "创建代理策略",

View file

@ -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',