Skip to content

Commit 15fec9e

Browse files
juliaElasticcriamicokibanamachine
authored
[8.19][Fleet] Add license gate around agents automatic upgrades feature (#2… (#229752)
Backport #224393 to 8.19 --------- Co-authored-by: Cristina Amico <criamico@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent b6732ad commit 15fec9e

File tree

9 files changed

+88
-11
lines changed

9 files changed

+88
-11
lines changed

x-pack/platform/plugins/shared/fleet/public/applications/fleet/layouts/default/default.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { WithHeaderLayout } from '../../../../layouts';
1616

1717
import { ExperimentalFeaturesService } from '../../services';
1818
import { AutoUpgradeAgentsTour } from '../../sections/agent_policy/components/auto_upgrade_agents_tour';
19+
import { useCanEnableAutomaticAgentUpgrades } from '../../../../hooks/use_can_enable_auto_upgrades';
1920

2021
import { DefaultPageTitle } from './default_page_title';
2122

@@ -37,6 +38,7 @@ export const DefaultLayout: React.FunctionComponent<Props> = ({
3738

3839
const { docLinks } = useStartServices();
3940
const granularPrivilegesCallout = useDismissableTour('GRANULAR_PRIVILEGES');
41+
const canEnableAutomaticAgentUpgrades = useCanEnableAutomaticAgentUpgrades();
4042

4143
const tabs = [
4244
{
@@ -147,7 +149,9 @@ export const DefaultLayout: React.FunctionComponent<Props> = ({
147149
<WithHeaderLayout leftColumn={<DefaultPageTitle />} rightColumn={rightColumn} tabs={tabs}>
148150
{children}
149151
</WithHeaderLayout>
150-
<AutoUpgradeAgentsTour anchor="#fleet-agent-policies-tab" />
152+
{canEnableAutomaticAgentUpgrades ? (
153+
<AutoUpgradeAgentsTour anchor="#fleet-agent-policies-tab" />
154+
) : null}
151155
</>
152156
);
153157
};

x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { AgentUpgradeAgentModal } from '../../agents/components';
2424

2525
import { ManageAutoUpgradeAgentsModal } from '../../agents/components/manage_auto_upgrade_agents_modal';
2626

27+
import { useCanEnableAutomaticAgentUpgrades } from '../../../../../hooks/use_can_enable_auto_upgrades';
28+
2729
import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout';
2830
import { AgentPolicyCopyProvider } from './agent_policy_copy_provider';
2931
import { AgentPolicyDeleteProvider } from './agent_policy_delete_provider';
@@ -56,6 +58,7 @@ export const AgentPolicyActionMenu = memo<{
5658
const refreshAgentPolicy = useAgentPolicyRefresh();
5759

5860
const { agentTamperProtectionEnabled } = ExperimentalFeaturesService.get();
61+
const canEnableAutomaticAgentUpgrades = useCanEnableAutomaticAgentUpgrades();
5962

6063
const isFleetServerPolicy = useMemo(
6164
() =>
@@ -211,7 +214,7 @@ export const AgentPolicyActionMenu = memo<{
211214
)}
212215
</EuiContextMenuItem>,
213216
viewPolicyItem,
214-
manageAutoUpgradeAgentsItem,
217+
...(canEnableAutomaticAgentUpgrades ? [manageAutoUpgradeAgentsItem] : []),
215218
copyPolicyItem,
216219
deletePolicyItem,
217220
];

x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/details_page/components/header/right_content.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { FLEET_SERVER_PACKAGE } from '../../../../../../../../common/constants';
3131
import { getRootIntegrations } from '../../../../../../../../common/services';
3232
import { ManageAutoUpgradeAgentsModal } from '../../../../agents/components/manage_auto_upgrade_agents_modal';
3333
import { AutoUpgradeAgentsTour } from '../../../components/auto_upgrade_agents_tour';
34+
import { useCanEnableAutomaticAgentUpgrades } from '../../../../../../../hooks/use_can_enable_auto_upgrades';
3435

3536
import { ManageAutoUpgradeAgentsBadge } from './manage_auto_upgrade_agents';
3637

@@ -63,6 +64,7 @@ export const HeaderRightContent: React.FunctionComponent<HeaderRightContentProps
6364
const [isManageAutoUpgradeAgentsModalOpen, setIsManageAutoUpgradeAgentsModalOpen] =
6465
useState<boolean>(false);
6566
const refreshAgentPolicy = useAgentPolicyRefresh();
67+
const canEnableAutomaticAgentUpgrades = useCanEnableAutomaticAgentUpgrades();
6668

6769
const isFleetServerPolicy = useMemo(
6870
() =>
@@ -215,7 +217,7 @@ export const HeaderRightContent: React.FunctionComponent<HeaderRightContentProps
215217
'',
216218
},
217219
{ isDivider: true },
218-
...(authz.fleet.allAgentPolicies
220+
...(canEnableAutomaticAgentUpgrades && authz.fleet.allAgentPolicies
219221
? [
220222
{
221223
label: i18n.translate('xpack.fleet.policyDetails.summary.autoUpgrade', {
@@ -251,7 +253,7 @@ export const HeaderRightContent: React.FunctionComponent<HeaderRightContentProps
251253
{item.isDivider ?? false ? (
252254
<Divider />
253255
) : item.label ? (
254-
<EuiDescriptionList compressed textStyle="reverse" style={{ textAlign: 'right' }}>
256+
<EuiDescriptionList compressed textStyle="reverse" css={{ textAlign: 'right' }}>
255257
<EuiDescriptionListTitle className="eui-textNoWrap">
256258
{item.label}
257259
</EuiDescriptionListTitle>
@@ -279,7 +281,9 @@ export const HeaderRightContent: React.FunctionComponent<HeaderRightContentProps
279281
/>
280282
</EuiPortal>
281283
)}
282-
<AutoUpgradeAgentsTour anchor="#auto-upgrade-manage-button" />
284+
{canEnableAutomaticAgentUpgrades ? (
285+
<AutoUpgradeAgentsTour anchor="#auto-upgrade-manage-button" />
286+
) : null}
283287
</>
284288
);
285289
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { ExperimentalFeaturesService } from '../services';
9+
10+
import { licenseService } from './use_license';
11+
12+
export function useCanEnableAutomaticAgentUpgrades() {
13+
const { enableAutomaticAgentUpgrades } = ExperimentalFeaturesService.get();
14+
return enableAutomaticAgentUpgrades && licenseService.isEnterprise();
15+
}

x-pack/platform/plugins/shared/fleet/server/routes/agent_policy/handlers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { inputsFormat } from '../../../common/constants';
1717
import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header';
1818

1919
import { fullAgentPolicyToYaml } from '../../../common/services';
20-
import { appContextService, agentPolicyService } from '../../services';
20+
import { appContextService, agentPolicyService, licenseService } from '../../services';
2121
import { type AgentClient, getLatestAvailableAgentVersion } from '../../services/agents';
2222
import { AGENTS_PREFIX, UNPRIVILEGED_AGENT_KUERY } from '../../constants';
2323
import type {
@@ -278,6 +278,12 @@ export const getAutoUpgradeAgentsStatusHandler: FleetRequestHandler<
278278

279279
const agentClient = fleetContext.agentClient.asCurrentUser;
280280

281+
if (!licenseService.isEnterprise()) {
282+
throw new FleetUnauthorizedError(
283+
'Auto-upgrade agents feature requires at least Enterprise license'
284+
);
285+
}
286+
281287
const body = await getAutoUpgradeAgentsStatus(agentClient, request.params.agentPolicyId);
282288
return response.ok({
283289
body,

x-pack/platform/plugins/shared/fleet/server/services/agent_policies/required_versions.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
*/
77

88
import { appContextService } from '..';
9-
import { AgentPolicyInvalidError } from '../../errors';
9+
import { AgentPolicyInvalidError, FleetUnauthorizedError } from '../../errors';
10+
11+
import { licenseService } from '..';
1012

1113
import { validateRequiredVersions } from './required_versions';
1214

@@ -30,6 +32,10 @@ describe('validateRequiredVersions', () => {
3032
jest
3133
.spyOn(appContextService, 'getExperimentalFeatures')
3234
.mockReturnValue({ enableAutomaticAgentUpgrades: true } as any);
35+
jest.spyOn(licenseService, 'isEnterprise').mockReturnValue(true);
36+
});
37+
afterEach(() => {
38+
jest.spyOn(licenseService, 'isEnterprise').mockClear();
3339
});
3440

3541
it('should throw error if duplicate versions', () => {
@@ -45,6 +51,20 @@ describe('validateRequiredVersions', () => {
4551
);
4652
});
4753

54+
it('should throw error if license is not at least Enterprise', () => {
55+
jest.spyOn(licenseService, 'isEnterprise').mockReturnValue(false);
56+
expect(() => {
57+
validateRequiredVersions('test policy', [
58+
{ version: '9.0.0', percentage: 10 },
59+
{ version: '9.0.0', percentage: 10 },
60+
]);
61+
}).toThrow(
62+
new FleetUnauthorizedError(
63+
`Agents auto upgrades feature requires at least Enterprise license`
64+
)
65+
);
66+
});
67+
4868
it('should throw error if has invalid semver version', () => {
4969
expect(() => {
5070
validateRequiredVersions('test policy', [

x-pack/platform/plugins/shared/fleet/server/services/agent_policies/required_versions.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import type { AgentTargetVersion } from '../../../common/types';
99

10-
import { AgentPolicyInvalidError } from '../../errors';
11-
import { appContextService } from '..';
10+
import { AgentPolicyInvalidError, FleetUnauthorizedError } from '../../errors';
11+
import { appContextService, licenseService } from '..';
1212
import { checkTargetVersionsValidity } from '../../../common/services/agent_utils';
1313

1414
export function validateRequiredVersions(
@@ -23,6 +23,11 @@ export function validateRequiredVersions(
2323
`Policy "${name}" failed validation: required_versions are not allowed when automatic upgrades feature is disabled`
2424
);
2525
}
26+
if (requiredVersions && !licenseService.isEnterprise()) {
27+
throw new FleetUnauthorizedError(
28+
'Agents auto upgrades feature requires at least Enterprise license'
29+
);
30+
}
2631
const error = checkTargetVersionsValidity(requiredVersions);
2732
if (error) {
2833
throw new AgentPolicyInvalidError(

x-pack/platform/plugins/shared/fleet/server/tasks/automatic_agent_upgrade_task.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server';
1313
import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task';
1414

1515
import { createAppContextStartContractMock } from '../mocks';
16-
import { agentPolicyService, appContextService } from '../services';
16+
import { agentPolicyService, appContextService, licenseService } from '../services';
1717
import {
1818
fetchAllAgentsByKuery,
1919
getAgentsByKuery,
@@ -106,6 +106,8 @@ describe('AutomaticAgentUpgradeTask', () => {
106106
let mockTaskManagerSetup: jest.Mocked<TaskManagerSetupContract>;
107107

108108
beforeEach(() => {
109+
jest.spyOn(licenseService, 'isEnterprise').mockReturnValue(true);
110+
109111
mockContract = createAppContextStartContractMock();
110112
appContextService.start(mockContract);
111113
mockCore = coreSetupMock();
@@ -119,6 +121,7 @@ describe('AutomaticAgentUpgradeTask', () => {
119121

120122
afterEach(() => {
121123
jest.clearAllMocks();
124+
jest.spyOn(licenseService, 'isEnterprise').mockClear();
122125
});
123126

124127
describe('Task lifecycle', () => {
@@ -177,6 +180,14 @@ describe('AutomaticAgentUpgradeTask', () => {
177180
expect(mockAgentPolicyService.fetchAllAgentPolicies).not.toHaveBeenCalled();
178181
});
179182

183+
it('Should exit if the license is not at least Enterprise', async () => {
184+
jest.spyOn(licenseService, 'isEnterprise').mockReturnValue(false);
185+
186+
await runTask();
187+
188+
expect(mockAgentPolicyService.fetchAllAgentPolicies).not.toHaveBeenCalled();
189+
});
190+
180191
it('Should upgrade eligible agents', async () => {
181192
const agents = generateAgents(10);
182193
mockedGetAgentsByKuery

x-pack/platform/plugins/shared/fleet/server/tasks/automatic_agent_upgrade_task.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import type {
3131
FleetServerAgentMetadata,
3232
} from '../../common/types';
3333

34-
import { agentPolicyService, appContextService } from '../services';
34+
import { agentPolicyService, appContextService, licenseService } from '../services';
3535
import {
3636
fetchAllAgentsByKuery,
3737
getAgentsByKuery,
@@ -132,6 +132,12 @@ export class AutomaticAgentUpgradeTask {
132132
);
133133
return;
134134
}
135+
if (!licenseService.isEnterprise()) {
136+
this.logger.debug(
137+
'[AutomaticAgentUpgradeTask] Aborting runTask: automatic upgrades feature requires at least Enterprise license'
138+
);
139+
return;
140+
}
135141

136142
if (!this.wasStarted) {
137143
this.logger.debug('[AutomaticAgentUpgradeTask] Aborting runTask(): task not started yet');
@@ -369,6 +375,9 @@ export class AutomaticAgentUpgradeTask {
369375

370376
numberOfAgentsForUpgrade -= numberOfRetriedAgents;
371377
if (numberOfAgentsForUpgrade <= 0) {
378+
this.logger.debug(
379+
`[AutomaticAgentUpgradeTask] Number of agents ${numberOfAgentsForUpgrade}: no candidate agents found for upgrade (target version: ${requiredVersion.version}, percentage: ${requiredVersion.percentage})`
380+
);
372381
return;
373382
}
374383

0 commit comments

Comments
 (0)