= memo(({ err
superuser }}
+ defaultMessage="You are not authorized to access Fleet. It requires the {roleName1} Kibana privilege for Fleet, and the {roleName2} or {roleName1} privilege for Integrations."
+ values={{
+ roleName1: "All",
+ roleName2: "Read",
+ }}
/>
}
@@ -124,7 +129,10 @@ const PermissionsError: React.FunctionComponent<{ error: string }> = memo(({ err
export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
useBreadcrumbs('base');
- const { notifications } = useStartServices();
+ const core = useStartServices();
+ const { notifications } = core;
+
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const [isPermissionsLoading, setIsPermissionsLoading] = useState(false);
const [permissionsError, setPermissionsError] = useState();
@@ -156,6 +164,9 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
}),
});
}
+ if (!hasFleetAllPrivileges) {
+ setPermissionsError('MISSING_PRIVILEGES');
+ }
} catch (err) {
setInitializationError(err);
}
@@ -167,7 +178,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
setPermissionsError('REQUEST_ERROR');
}
})();
- }, [notifications.toasts]);
+ }, [notifications.toasts, hasFleetAllPrivileges]);
if (isPermissionsLoading || permissionsError) {
return (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx
index 65b5bb2320c0..6e2dc54470a1 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/actions_menu.tsx
@@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
import type { AgentPolicy } from '../../../types';
-import { useCapabilities } from '../../../hooks';
+import { useAuthz } from '../../../hooks';
import { AgentEnrollmentFlyout, ContextMenuActions } from '../../../components';
import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout';
@@ -30,7 +30,7 @@ export const AgentPolicyActionMenu = memo<{
enrollmentFlyoutOpenByDefault = false,
onCancelEnrollment,
}) => {
- const hasWriteCapabilities = useCapabilities().write;
+ const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false);
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(
enrollmentFlyoutOpenByDefault
@@ -76,7 +76,6 @@ export const AgentPolicyActionMenu = memo<{
? [viewPolicyItem]
: [
{
setIsContextMenuOpen(false);
@@ -91,7 +90,7 @@ export const AgentPolicyActionMenu = memo<{
,
viewPolicyItem,
{
setIsContextMenuOpen(false);
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx
index 3355fce7ff2a..692752e25938 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx
@@ -28,7 +28,7 @@ import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../..
import {
useGetAgentPolicies,
sendGetOneAgentPolicy,
- useCapabilities,
+ useAuthz,
useFleetStatus,
} from '../../../hooks';
import { CreateAgentPolicyFlyout } from '../list_page/components';
@@ -63,7 +63,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState();
// Create new agent policy flyout state
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const [isCreateAgentPolicyFlyoutOpen, setIsCreateAgentPolicyFlyoutOpen] =
useState(false);
@@ -251,7 +251,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
setIsCreateAgentPolicyFlyoutOpen(true)}
>
(({ policyId }) => {
const { application } = useStartServices();
- const hasWriteCapabilities = useCapabilities().write;
+ const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
return (
(({ policyId }) => {
}
actions={
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
index 09ecfc94ab7b..5ccea47a913a 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
@@ -25,12 +25,7 @@ import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../common';
import { pagePathGetters } from '../../../../../../../constants';
import type { AgentPolicy, InMemoryPackagePolicy, PackagePolicy } from '../../../../../types';
import { PackageIcon, PackagePolicyActionsMenu } from '../../../../../components';
-import {
- useCapabilities,
- useLink,
- usePackageInstallations,
- useStartServices,
-} from '../../../../../hooks';
+import { useAuthz, useLink, usePackageInstallations, useStartServices } from '../../../../../hooks';
import { pkgKeyFromPackageInfo } from '../../../../../services';
interface Props {
@@ -55,7 +50,8 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
...rest
}) => {
const { application } = useStartServices();
- const hasWriteCapabilities = useCapabilities().write;
+ const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
+ const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies;
const { updatableIntegrations } = usePackageInstallations();
const { getHref } = useLink();
@@ -109,7 +105,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
render: (value: string, packagePolicy: InMemoryPackagePolicy) => (
= ({
render(packageTitle: string, packagePolicy: InMemoryPackagePolicy) {
return (
-
+
= ({
= ({
actions: [
{
render: (packagePolicy: InMemoryPackagePolicy) => {
- return (
+ return canWriteIntegrationPolicies ? (
= ({
packagePolicyId: packagePolicy.id,
})}?from=fleet-policy-list`}
/>
+ ) : (
+ <>>
);
},
},
],
},
],
- [agentPolicy, getHref, hasWriteCapabilities]
+ [agentPolicy, getHref, canWriteIntegrationPolicies, canReadIntegrationPolicies]
);
return (
@@ -268,7 +267,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
{
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
index 0b4c61273cb5..e70ac711d879 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
@@ -23,7 +23,7 @@ import type { AgentPolicy } from '../../../../../types';
import {
useLink,
useStartServices,
- useCapabilities,
+ useAuthz,
sendUpdateAgentPolicy,
useConfig,
sendGetAgentStatus,
@@ -51,7 +51,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
} = useConfig();
const history = useHistory();
const { getPath } = useLink();
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const refreshAgentPolicy = useAgentPolicyRefresh();
const [agentPolicy, setAgentPolicy] = useState({
...originalAgentPolicy,
@@ -186,7 +186,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
onClick={onSubmit}
isLoading={isLoading}
isDisabled={
- !hasWriteCapabilites || isLoading || Object.keys(validation).length > 0
+ !hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0
}
iconType="save"
color="primary"
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
index c953b480b806..4bce393aebb1 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
@@ -12,7 +12,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { safeLoad } from 'js-yaml';
import {
EuiButtonEmpty,
- EuiButton,
EuiBottomBar,
EuiCallOut,
EuiFlexGroup,
@@ -42,6 +41,7 @@ import {
sendGetOnePackagePolicy,
sendGetPackageInfoByKey,
sendUpgradePackagePolicyDryRun,
+ useAuthz,
} from '../../../hooks';
import {
useBreadcrumbs as useIntegrationsBreadcrumbs,
@@ -65,6 +65,8 @@ import type {
import type { PackagePolicyEditExtensionComponentProps } from '../../../types';
import { pkgKeyFromPackageInfo, storedPackagePoliciesToAgentInputs } from '../../../services';
+import { EuiButtonWithTooltip } from '../../../../integrations/sections/epm/screens/detail';
+
import { hasUpgradeAvailable } from './utils';
export const EditPackagePolicyPage = memo(() => {
@@ -123,6 +125,8 @@ export const EditPackagePolicyForm = memo<{
const [isUpgrade, setIsUpgrade] = useState(false);
+ const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
+
useEffect(() => {
if (forceUpgrade) {
setIsUpgrade(true);
@@ -625,11 +629,27 @@ export const EditPackagePolicyForm = memo<{
-
+ ),
+ }
+ : undefined
+ }
iconType="save"
color="primary"
fill
@@ -646,7 +666,7 @@ export const EditPackagePolicyForm = memo<{
defaultMessage="Save integration"
/>
)}
-
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx
index e420b3ce4ce9..7b20ce801afc 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx
@@ -26,7 +26,7 @@ import {
import { dataTypes } from '../../../../../../../common';
import type { NewAgentPolicy, AgentPolicy } from '../../../../types';
-import { useCapabilities, useStartServices, sendCreateAgentPolicy } from '../../../../hooks';
+import { useAuthz, useStartServices, sendCreateAgentPolicy } from '../../../../hooks';
import { AgentPolicyForm, agentPolicyFormValidation } from '../../components';
const FlyoutWithHigherZIndex = styled(EuiFlyout)`
@@ -43,7 +43,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({
...restOfProps
}) => {
const { notifications } = useStartServices();
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const [agentPolicy, setAgentPolicy] = useState({
name: '',
description: '',
@@ -115,7 +115,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({
0}
+ isDisabled={!hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0}
onClick={async () => {
setIsLoading(true);
try {
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx
index 6a07be853f3c..d413d1c1e721 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx
@@ -25,7 +25,7 @@ import { useHistory } from 'react-router-dom';
import type { AgentPolicy } from '../../../types';
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants';
import {
- useCapabilities,
+ useAuthz,
useGetAgentPolicies,
usePagination,
useSorting,
@@ -42,7 +42,8 @@ import { CreateAgentPolicyFlyout } from './components';
export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
useBreadcrumbs('policies_list');
const { getPath } = useLink();
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
+
const {
agents: { enabled: isFleetEnabled },
} = useConfig();
@@ -148,6 +149,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
packagePolicies ? packagePolicies.length : 0,
},
{
+ field: 'actions',
name: i18n.translate('xpack.fleet.agentPolicyList.actionsColumnTitle', {
defaultMessage: 'Actions',
}),
@@ -177,7 +179,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
setIsCreateAgentPolicyFlyoutOpen(true)}
>
= () => {
/>
),
- [hasWriteCapabilites, setIsCreateAgentPolicyFlyoutOpen]
+ [hasFleetAllPrivileges, setIsCreateAgentPolicyFlyoutOpen]
);
const emptyPrompt = useMemo(
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
index 0f1c70f7cb48..1c5d8ada75ef 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
@@ -10,7 +10,7 @@ import { EuiPortal, EuiContextMenuItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { Agent, AgentPolicy, PackagePolicy } from '../../../../types';
-import { useCapabilities, useKibanaVersion } from '../../../../hooks';
+import { useAuthz, useKibanaVersion } from '../../../../hooks';
import { ContextMenuActions } from '../../../../components';
import {
AgentUnenrollAgentModal,
@@ -27,7 +27,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
assignFlyoutOpenByDefault?: boolean;
onCancelReassign?: () => void;
}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign, agentPolicy }) => {
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const kibanaVersion = useKibanaVersion();
const refreshAgent = useAgentRefresh();
const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault);
@@ -110,7 +110,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
,
{
setIsUnenrollModalOpen(true);
}}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
index f90db1a3a642..96ad84a66e98 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
@@ -139,6 +139,7 @@ export const AgentDetailsIntegration: React.FunctionComponent<{
void;
}>(({ agent, agentPolicy, refresh, onReassignClick, onUnenrollClick, onUpgradeClick }) => {
const { getHref } = useLink();
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const isUnenrolling = agent.status === 'unenrolling';
const kibanaVersion = useKibanaVersion();
@@ -99,7 +99,7 @@ const RowActions = React.memo<{
/>
,
{
onUnenrollClick();
@@ -152,7 +152,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
useBreadcrumbs('agent_list');
const { getHref } = useLink();
const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || '';
- const hasWriteCapabilites = useCapabilities().write;
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const isGoldPlus = useLicense().isGoldPlus();
const kibanaVersion = useKibanaVersion();
@@ -507,7 +507,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
}
actions={
- hasWriteCapabilites ? (
+ hasFleetAllPrivileges ? (
{
const startService = useStartServices();
const deploymentUrl = startService.cloud?.deploymentUrl;
+ const [isPermissionsLoading, setIsPermissionsLoading] = useState(false);
+ const [permissionsError, setPermissionsError] = useState();
+
+ useEffect(() => {
+ async function checkPermissions() {
+ setIsPermissionsLoading(false);
+ setPermissionsError(undefined);
+
+ try {
+ setIsPermissionsLoading(true);
+ const permissionsResponse = await sendGetPermissionsCheck(true);
+
+ setIsPermissionsLoading(false);
+ if (!permissionsResponse.data?.success) {
+ setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR');
+ }
+ } catch (err) {
+ setPermissionsError('REQUEST_ERROR');
+ }
+ }
+ checkPermissions();
+ }, []);
+
return (
<>
{
{deploymentUrl ? (
+ ) : isPermissionsLoading ? (
+
+ ) : permissionsError ? (
+
) : (
)}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/fleet_server_missing_privileges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/fleet_server_missing_privileges.tsx
new file mode 100644
index 000000000000..41e02376898c
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/fleet_server_missing_privileges.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 React from 'react';
+import { EuiCode, EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+import styled from 'styled-components';
+
+const Panel = styled(EuiPanel)`
+ max-width: 500px;
+ margin-right: auto;
+ margin-left: auto;
+`;
+
+export const FleetServerMissingPrivileges = () => {
+ return (
+
+
+
+
+ }
+ body={
+
+ "manage_service_account",
+ }}
+ />
+
+ }
+ />
+
+ );
+};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/index.tsx
index 1346153d0b34..04f4c3243706 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_callouts/index.tsx
@@ -8,3 +8,4 @@
export * from './fleet_server_cloud_unhealthy_callout';
export * from './fleet_server_on_prem_unhealthy_callout';
export * from './fleet_server_on_prem_required_callout';
+export * from './fleet_server_missing_privileges';
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx
index 8bb8cf6094ef..43468a30d6a5 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/error_pages/no_access.tsx
@@ -22,7 +22,7 @@ export const NoAccessPage = injectI18n(({ intl }) => (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx
index 3f9f9220c501..c56e9b33ffbc 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx
@@ -16,7 +16,7 @@ import {
useConfig,
useFleetStatus,
useBreadcrumbs,
- useCapabilities,
+ useAuthz,
useGetSettings,
useGetAgentPolicies,
} from '../../hooks';
@@ -32,7 +32,7 @@ export const AgentsApp: React.FunctionComponent = () => {
useBreadcrumbs('agent_list');
const history = useHistory();
const { agents } = useConfig();
- const capabilities = useCapabilities();
+ const hasFleetAllPrivileges = useAuthz().fleet.all;
const agentPoliciesRequest = useGetAgentPolicies({
page: 1,
@@ -93,7 +93,7 @@ export const AgentsApp: React.FunctionComponent = () => {
) {
return ;
}
- if (!capabilities.read) {
+ if (!hasFleetAllPrivileges) {
return ;
}
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx
index 02874f12c659..1796173c9885 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx
@@ -85,7 +85,7 @@ describe('when on integration detail', () => {
expect(renderResult.queryByTestId('agentPolicyCount')).toBeNull();
});
- it('should NOT the Policies tab', async () => {
+ it('should NOT display the Policies tab', async () => {
await mockedApi.waitForApi();
expect(renderResult.queryByTestId('tab-policies')).toBeNull();
});
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
index 507ed57006c4..2a1c9a29f289 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
@@ -35,6 +35,7 @@ import {
useUIExtension,
useBreadcrumbs,
useStartServices,
+ useAuthz,
usePermissionCheck,
} from '../../../../hooks';
import {
@@ -43,12 +44,7 @@ import {
INTEGRATIONS_ROUTING_PATHS,
pagePathGetters,
} from '../../../../constants';
-import {
- useCapabilities,
- useGetPackageInfoByKey,
- useLink,
- useAgentPolicyContext,
-} from '../../../../hooks';
+import { useGetPackageInfoByKey, useLink, useAgentPolicyContext } from '../../../../hooks';
import { pkgKeyFromPackageInfo } from '../../../../services';
import type {
CreatePackagePolicyRouteState,
@@ -102,11 +98,13 @@ export function Detail() {
const { getId: getAgentPolicyId } = useAgentPolicyContext();
const { pkgkey, panel } = useParams();
const { getHref } = useLink();
- const hasWriteCapabilities = useCapabilities().write;
+ const canInstallPackages = useAuthz().integrations.installPackages;
+ const canReadPackageSettings = useAuthz().integrations.readPackageSettings;
+ const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies;
const permissionCheck = usePermissionCheck();
const missingSecurityConfiguration =
!permissionCheck.data?.success && permissionCheck.data?.error === 'MISSING_SECURITY';
- const userCanInstallIntegrations = hasWriteCapabilities && permissionCheck.data?.success;
+ const userCanInstallPackages = canInstallPackages && permissionCheck.data?.success;
const history = useHistory();
const { pathname, search, hash } = useLocation();
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
@@ -367,7 +365,7 @@ export function Detail() {
content: (
) : (
),
}
@@ -427,7 +425,7 @@ export function Detail() {
packageInfo,
updateAvailable,
packageInstallStatus,
- userCanInstallIntegrations,
+ userCanInstallPackages,
getHref,
pkgkey,
integration,
@@ -462,7 +460,7 @@ export function Detail() {
},
];
- if (userCanInstallIntegrations && packageInstallStatus === InstallStatus.installed) {
+ if (canReadIntegrationPolicies && packageInstallStatus === InstallStatus.installed) {
tabs.push({
id: 'policies',
name: (
@@ -498,7 +496,7 @@ export function Detail() {
});
}
- if (userCanInstallIntegrations) {
+ if (canReadPackageSettings) {
tabs.push({
id: 'settings',
name: (
@@ -540,7 +538,8 @@ export function Detail() {
panel,
getHref,
integration,
- userCanInstallIntegrations,
+ canReadIntegrationPolicies,
+ canReadPackageSettings,
packageInstallStatus,
CustomAssets,
showCustomTab,
@@ -628,7 +627,7 @@ export function Detail() {
type EuiButtonPropsFull = Parameters[0];
-const EuiButtonWithTooltip: React.FC<
+export const EuiButtonWithTooltip: React.FC<
EuiButtonPropsFull & { tooltip?: Partial }
> = ({ tooltip: tooltipProps, ...buttonProps }) => {
return tooltipProps ? (
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
index 0007679398b8..d6463c7fd5d8 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
@@ -33,6 +33,7 @@ import {
AgentPolicyRefreshContext,
useUIExtension,
usePackageInstallations,
+ useAuthz,
} from '../../../../../hooks';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
import {
@@ -104,6 +105,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
const { updatableIntegrations } = usePackageInstallations();
const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout');
+ const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
+
const packageAndAgentPolicies = useMemo((): Array<{
agentPolicy: GetAgentPoliciesResponseItem;
packagePolicy: InMemoryPackagePolicy;
@@ -244,6 +247,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
policyId: agentPolicy.id,
packagePolicyId: packagePolicy.id,
})}?from=integrations-policy-list`}
+ isDisabled={!canWriteIntegrationPolicies}
>
{
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx
index 5d5dfbda7bec..a2067038794b 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx
@@ -11,11 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../../../types';
import { InstallStatus } from '../../../../../types';
-import {
- useCapabilities,
- useGetPackageInstallStatus,
- useInstallPackage,
-} from '../../../../../hooks';
+import { useAuthz, useGetPackageInstallStatus, useInstallPackage } from '../../../../../hooks';
import { ConfirmPackageInstall } from './confirm_package_install';
@@ -30,7 +26,7 @@ type InstallationButtonProps = Pick &
};
export function InstallButton(props: InstallationButtonProps) {
const { name, numOfAssets, title, version } = props;
- const hasWriteCapabilites = useCapabilities().write;
+ const canInstallPackages = useAuthz().integrations.installPackages;
const installPackage = useInstallPackage();
const getPackageInstallStatus = useGetPackageInstallStatus();
const { status: installationStatus } = getPackageInstallStatus(name);
@@ -56,7 +52,7 @@ export function InstallButton(props: InstallationButtonProps) {
/>
);
- return hasWriteCapabilites ? (
+ return canInstallPackages ? (
{isInstalling ? (
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx
index cf9482a080ae..73780e23a1a7 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx
@@ -12,11 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { InstallStatus } from '../../../../../types';
import type { PackageInfo } from '../../../../../types';
-import {
- useCapabilities,
- useGetPackageInstallStatus,
- useUninstallPackage,
-} from '../../../../../hooks';
+import { useAuthz, useGetPackageInstallStatus, useUninstallPackage } from '../../../../../hooks';
import { ConfirmPackageUninstall } from './confirm_package_uninstall';
@@ -34,7 +30,7 @@ export const UninstallButton: React.FunctionComponent = ({
title,
version,
}) => {
- const hasWriteCapabilites = useCapabilities().write;
+ const canRemovePackages = useAuthz().integrations.removePackages;
const uninstallPackage = useUninstallPackage();
const getPackageInstallStatus = useGetPackageInstallStatus();
const { status: installationStatus } = getPackageInstallStatus(name);
@@ -59,7 +55,7 @@ export const UninstallButton: React.FunctionComponent = ({
/>
);
- return hasWriteCapabilites ? (
+ return canRemovePackages ? (
<>
= ({
const { getPath } = useLink();
const { notifications } = useStartServices();
- const hasWriteCapabilites = useCapabilities().write;
+ const canUpgradePackages = useAuthz().integrations.upgradePackages;
const installPackage = useInstallPackage();
const getPackageInstallStatus = useGetPackageInstallStatus();
@@ -287,7 +287,7 @@ export const UpdateButton: React.FunctionComponent = ({
);
- return hasWriteCapabilites ? (
+ return (
<>
@@ -297,6 +297,7 @@ export const UpdateButton: React.FunctionComponent = ({
upgradePackagePolicies ? () => setIsUpdateModalVisible(true) : handleClickUpdate
}
data-test-subj="updatePackageBtn"
+ isDisabled={!canUpgradePackages}
>
= ({
},
}}
id="upgradePoliciesCheckbox"
+ disabled={!canUpgradePackages}
checked={upgradePackagePolicies}
onChange={handleUpgradePackagePoliciesChange}
label={i18n.translate(
@@ -329,5 +331,5 @@ export const UpdateButton: React.FunctionComponent = ({
{isUpdateModalVisible && updateModal}
>
- ) : null;
+ );
};
diff --git a/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx b/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx
index 4708239bc22b..f72cb809f4c6 100644
--- a/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx
+++ b/x-pack/plugins/fleet/public/components/home_integration/tutorial_directory_header_link.tsx
@@ -11,17 +11,17 @@ import { EuiButtonEmpty } from '@elastic/eui';
import type { TutorialDirectoryHeaderLinkComponent } from 'src/plugins/home/public';
import { RedirectAppLinks } from '../../../../../../src/plugins/kibana_react/public';
-import { useLink, useCapabilities, useStartServices } from '../../hooks';
+import { useLink, useStartServices } from '../../hooks';
const TutorialDirectoryHeaderLink: TutorialDirectoryHeaderLinkComponent = memo(() => {
const { getHref } = useLink();
const { application } = useStartServices();
- const { show: hasIngestManager } = useCapabilities();
+ const hasIntegrationsPermissions = application.capabilities.navLinks.integrations;
const [noticeState] = useState({
settingsDataLoaded: false,
});
- return hasIngestManager && noticeState.settingsDataLoaded ? (
+ return hasIntegrationsPermissions && noticeState.settingsDataLoaded ? (
{
const { getHref } = useLink();
- const { show: hasIngestManager } = useCapabilities();
+ const { application } = useStartServices();
+ const hasIntegrationsPermissions = application.capabilities.navLinks.integrations;
const { data: packagesData, isLoading } = useGetPackages();
const pkgInfo =
@@ -25,7 +26,7 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }
packagesData?.response &&
packagesData.response.find((pkg) => pkg.name === moduleName && pkg.name !== FLEET_APM_PACKAGE); // APM needs special handling
- if (hasIngestManager && pkgInfo) {
+ if (hasIntegrationsPermissions && pkgInfo) {
return (
<>
diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx
index 6d090c886c46..9b534ff26ab7 100644
--- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx
+++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx
@@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { AgentPolicy, InMemoryPackagePolicy } from '../types';
-import { useAgentPolicyRefresh, useCapabilities, useLink } from '../hooks';
+import { useAgentPolicyRefresh, useAuthz, useLink } from '../hooks';
import { AgentEnrollmentFlyout } from './agent_enrollment_flyout';
import { ContextMenuActions } from './context_menu_actions';
@@ -36,7 +36,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
}) => {
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
const { getHref } = useLink();
- const hasWriteCapabilities = useCapabilities().write;
+ const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
const refreshAgentPolicy = useAgentPolicyRefresh();
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(defaultIsOpen);
@@ -75,7 +75,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
]
: []),
,
{
return (
{
deletePackagePoliciesPrompt([packagePolicy.id], () => {
diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts
index fa1f09fbf0b7..08befa46adae 100644
--- a/x-pack/plugins/fleet/public/hooks/index.ts
+++ b/x-pack/plugins/fleet/public/hooks/index.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-export { useCapabilities } from './use_capabilities';
+export { useAuthz } from './use_authz';
export { useStartServices } from './use_core';
export { useConfig, ConfigContext } from './use_config';
export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version';
diff --git a/x-pack/plugins/fleet/public/hooks/use_capabilities.ts b/x-pack/plugins/fleet/public/hooks/use_authz.ts
similarity index 72%
rename from x-pack/plugins/fleet/public/hooks/use_capabilities.ts
rename to x-pack/plugins/fleet/public/hooks/use_authz.ts
index 2da74f61ebe0..c7a177c34dce 100644
--- a/x-pack/plugins/fleet/public/hooks/use_capabilities.ts
+++ b/x-pack/plugins/fleet/public/hooks/use_authz.ts
@@ -7,7 +7,8 @@
import { useStartServices } from './use_core';
-export function useCapabilities() {
+// Expose authz object, containing the privileges for Fleet and Integrations
+export function useAuthz() {
const core = useStartServices();
- return core.application.capabilities.fleet;
+ return core.authz;
}
diff --git a/x-pack/plugins/fleet/public/hooks/use_request/app.ts b/x-pack/plugins/fleet/public/hooks/use_request/app.ts
index c4a0f9486982..25c7a9cf1f04 100644
--- a/x-pack/plugins/fleet/public/hooks/use_request/app.ts
+++ b/x-pack/plugins/fleet/public/hooks/use_request/app.ts
@@ -10,10 +10,11 @@ import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../
import { sendRequest, useRequest } from './use_request';
-export const sendGetPermissionsCheck = () => {
+export const sendGetPermissionsCheck = (fleetServerSetup?: boolean) => {
return sendRequest({
path: appRoutesService.getCheckPermissionsPath(),
method: 'get',
+ query: { fleetServerSetup },
});
};
diff --git a/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx b/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx
index 4d8f74fa3b04..edc9ac8b8fb3 100644
--- a/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx
+++ b/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx
@@ -55,12 +55,18 @@ const configureStartServices = (services: MockedFleetStartServices): void => {
// Store the http for use by useRequest
setHttpClient(services.http);
- // Set Fleet available capabilities
+ // Set Fleet and Integrations capabilities
services.application.capabilities = {
...services.application.capabilities,
+ // Fleet
+ fleetv2: {
+ read: true,
+ all: true,
+ },
+ // Integration
fleet: {
read: true,
- write: true,
+ all: true,
},
};
diff --git a/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts b/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts
index 68e3df17bbce..7a23e066e286 100644
--- a/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts
+++ b/x-pack/plugins/fleet/public/mock/plugin_interfaces.ts
@@ -14,7 +14,7 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo
return {
isInitialized: jest.fn().mockResolvedValue(true),
registerExtension: createExtensionRegistrationCallback(extensionsStorage),
- authz: Promise.resolve({
+ authz: {
fleet: {
all: true,
setup: true,
@@ -33,6 +33,6 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo
readIntegrationPolicies: true,
writeIntegrationPolicies: true,
},
- }),
+ },
};
};
diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts
index 548319e7bfab..385ef2bee651 100644
--- a/x-pack/plugins/fleet/public/plugin.ts
+++ b/x-pack/plugins/fleet/public/plugin.ts
@@ -76,7 +76,7 @@ export interface FleetSetup {}
*/
export interface FleetStart {
/** Authorization for the current user */
- authz: Promise;
+ authz: FleetAuthz;
registerExtension: UIExtensionRegistrationCallback;
isInitialized: () => Promise;
}
@@ -261,34 +261,21 @@ export class FleetPlugin implements Plugin {
- // eslint-disable-next-line no-console
- console.warn(`Could not load Fleet permissions due to error: ${e}`);
- return { success: false };
- })
- .then((permissionsResponse) => {
- if (permissionsResponse?.success) {
- // If superuser, give access to everything
- return calculateAuthz({
- fleet: { all: true, setup: true },
- integrations: { all: true, read: true },
- isSuperuser: true,
- });
- } else {
- // All other users only get access to read integrations if they have the read privilege
- const { capabilities } = core.application;
- return calculateAuthz({
- fleet: { all: false, setup: false },
- integrations: { all: false, read: capabilities.fleet.read as boolean },
- isSuperuser: false,
- });
- }
- }),
+ authz: calculateAuthz({
+ fleet: {
+ all: capabilities.fleetv2.all as boolean,
+ setup: false,
+ },
+ integrations: {
+ all: capabilities.fleet.all as boolean,
+ read: capabilities.fleet.read as boolean,
+ },
+ isSuperuser: false,
+ }),
isInitialized: once(async () => {
const permissionsResponse = await getPermissions();
diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts
index 51802c96791b..4f7c13170370 100644
--- a/x-pack/plugins/fleet/server/plugin.ts
+++ b/x-pack/plugins/fleet/server/plugin.ts
@@ -7,6 +7,8 @@
import type { Observable } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
+
+import { i18n } from '@kbn/i18n';
import type {
CoreSetup,
CoreStart,
@@ -222,14 +224,16 @@ export class FleetPlugin
registerEncryptedSavedObjects(deps.encryptedSavedObjects);
// Register feature
- // TODO: Flesh out privileges
if (deps.features) {
deps.features.registerKibanaFeature({
- id: PLUGIN_ID,
- name: 'Fleet and Integrations',
+ id: `fleetv2`,
+ name: 'Fleet',
category: DEFAULT_APP_CATEGORIES.management,
- app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
+ app: [PLUGIN_ID],
catalogue: ['fleet'],
+ privilegesTooltip: i18n.translate('xpack.fleet.serverPlugin.privilegesTooltip', {
+ defaultMessage: 'All Spaces is required for Fleet access.',
+ }),
reserved: {
description:
'Privilege to setup Fleet packages and configured policies. Intended for use by the elastic/fleet-server service account only.',
@@ -250,24 +254,64 @@ export class FleetPlugin
},
privileges: {
all: {
- api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`, `integrations-all`, `integrations-read`],
- app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
+ api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`],
+ app: [PLUGIN_ID],
+ requireAllSpaces: true,
catalogue: ['fleet'],
savedObject: {
all: allSavedObjectTypes,
read: [],
},
- ui: ['show', 'read', 'write'],
+ ui: ['read', 'all'],
},
read: {
- api: [`${PLUGIN_ID}-read`, `integrations-read`],
- app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
- catalogue: ['fleet'], // TODO: check if this is actually available to read user
+ api: [`${PLUGIN_ID}-read`],
+ app: [PLUGIN_ID],
+ catalogue: ['fleet'],
+ requireAllSpaces: true,
savedObject: {
all: [],
read: allSavedObjectTypes,
},
- ui: ['show', 'read'],
+ ui: ['read'],
+ disabled: true,
+ },
+ },
+ });
+
+ deps.features.registerKibanaFeature({
+ id: 'fleet', // for BWC
+ name: 'Integrations',
+ category: DEFAULT_APP_CATEGORIES.management,
+ app: [INTEGRATIONS_PLUGIN_ID],
+ catalogue: ['fleet'],
+ privilegesTooltip: i18n.translate(
+ 'xpack.fleet.serverPlugin.integrationsPrivilegesTooltip',
+ {
+ defaultMessage: 'All Spaces is required for All Integrations access.',
+ }
+ ),
+ privileges: {
+ all: {
+ api: [`${INTEGRATIONS_PLUGIN_ID}-read`, `${INTEGRATIONS_PLUGIN_ID}-all`],
+ app: [INTEGRATIONS_PLUGIN_ID],
+ catalogue: ['fleet'],
+ requireAllSpaces: true,
+ savedObject: {
+ all: allSavedObjectTypes,
+ read: [],
+ },
+ ui: ['read', 'all'],
+ },
+ read: {
+ api: [`${INTEGRATIONS_PLUGIN_ID}-read`],
+ app: [INTEGRATIONS_PLUGIN_ID],
+ catalogue: ['fleet'],
+ savedObject: {
+ all: [],
+ read: allSavedObjectTypes,
+ },
+ ui: ['read'],
},
},
});
diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
index 9aabeb026f29..ef39a2760e93 100644
--- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
+++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
@@ -105,7 +105,7 @@ export const createAgentPolicyHandler: FleetRequestHandler<
TypeOf,
TypeOf
> = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
+ const soClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
const withSysMonitoring = request.query.sys_monitoring ?? false;
@@ -229,11 +229,11 @@ export const deleteAgentPoliciesHandler: RequestHandler<
}
};
-export const getFullAgentPolicy: RequestHandler<
+export const getFullAgentPolicy: FleetRequestHandler<
TypeOf,
TypeOf
> = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
+ const soClient = context.fleet.epm.internalSoClient;
if (request.query.kubernetes === true) {
try {
@@ -284,11 +284,11 @@ export const getFullAgentPolicy: RequestHandler<
}
};
-export const downloadFullAgentPolicy: RequestHandler<
+export const downloadFullAgentPolicy: FleetRequestHandler<
TypeOf,
TypeOf
> = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
+ const soClient = context.fleet.epm.internalSoClient;
const {
params: { agentPolicyId },
} = request;
diff --git a/x-pack/plugins/fleet/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts
index 6d5b5f0cf301..b9f9f0ee494f 100644
--- a/x-pack/plugins/fleet/server/routes/app/index.ts
+++ b/x-pack/plugins/fleet/server/routes/app/index.ts
@@ -6,14 +6,20 @@
*/
import type { RequestHandler } from 'src/core/server';
+import type { TypeOf } from '@kbn/config-schema';
import { APP_API_ROUTES } from '../../constants';
import { appContextService } from '../../services';
import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../../common';
import { defaultIngestErrorHandler, GenerateServiceTokenError } from '../../errors';
import type { FleetAuthzRouter } from '../security';
+import type { FleetRequestHandler } from '../../types';
+import { CheckPermissionsRequestSchema } from '../../types';
-export const getCheckPermissionsHandler: RequestHandler = async (context, request, response) => {
+export const getCheckPermissionsHandler: FleetRequestHandler<
+ unknown,
+ TypeOf
+> = async (context, request, response) => {
const missingSecurityBody: CheckPermissionsResponse = {
success: false,
error: 'MISSING_SECURITY',
@@ -22,25 +28,32 @@ export const getCheckPermissionsHandler: RequestHandler = async (context, reques
if (!appContextService.getSecurityLicense().isEnabled()) {
return response.ok({ body: missingSecurityBody });
} else {
- const security = appContextService.getSecurity();
- const user = security.authc.getCurrentUser(request);
-
- // Defensively handle situation where user is undefined (should only happen when ES security is disabled)
- // This should be covered by the `getSecurityLicense().isEnabled()` check above, but we leave this for robustness.
- if (!user) {
- return response.ok({
- body: missingSecurityBody,
- });
- }
-
- if (!user?.roles.includes('superuser')) {
+ if (!context.fleet.authz.fleet.all) {
return response.ok({
body: {
success: false,
- error: 'MISSING_SUPERUSER_ROLE',
+ error: 'MISSING_PRIVILEGES',
} as CheckPermissionsResponse,
});
}
+ // check the manage_service_account cluster privilege
+ else if (request.query.fleetServerSetup) {
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
+ const {
+ body: { has_all_requested: hasAllPrivileges },
+ } = await esClient.security.hasPrivileges({
+ body: { cluster: ['manage_service_account'] },
+ });
+
+ if (!hasAllPrivileges) {
+ return response.ok({
+ body: {
+ success: false,
+ error: 'MISSING_FLEET_SERVER_SETUP_PRIVILEGES',
+ } as CheckPermissionsResponse,
+ });
+ }
+ }
return response.ok({ body: { success: true } as CheckPermissionsResponse });
}
@@ -77,9 +90,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.get(
{
path: APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
- validate: {},
+ validate: CheckPermissionsRequestSchema,
options: { tags: [] },
- // no permission check for that route
},
getCheckPermissionsHandler
);
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts
index 830553aa24da..1908d38ab408 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts
@@ -86,7 +86,7 @@ export const createPackagePolicyHandler: FleetRequestHandler<
undefined,
TypeOf
> = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
+ const soClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
const { force, ...newPolicy } = request.body;
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/index.ts b/x-pack/plugins/fleet/server/routes/package_policy/index.ts
index 647b1cb03561..68cdfc26df53 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/index.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/index.ts
@@ -106,7 +106,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
path: PACKAGE_POLICY_API_ROUTES.DRYRUN_PATTERN,
validate: DryRunPackagePoliciesRequestSchema,
fleetAuthz: {
- integrations: { writeIntegrationPolicies: true },
+ integrations: { readIntegrationPolicies: true },
},
},
dryRunUpgradePackagePolicyHandler
diff --git a/x-pack/plugins/fleet/server/routes/security.test.ts b/x-pack/plugins/fleet/server/routes/security.test.ts
index 6ddf8ade3d98..67ffb074c705 100644
--- a/x-pack/plugins/fleet/server/routes/security.test.ts
+++ b/x-pack/plugins/fleet/server/routes/security.test.ts
@@ -19,7 +19,10 @@ import { makeRouterWithFleetAuthz } from './security';
function getCheckPrivilegesMockedImplementation(kibanaRoles: string[]) {
return (checkPrivileges: CheckPrivilegesPayload) => {
const kibana = ((checkPrivileges?.kibana ?? []) as string[]).map((role: string) => {
- return { authorized: kibanaRoles.includes(role) };
+ return {
+ privilege: role,
+ authorized: kibanaRoles.includes(role),
+ };
});
return Promise.resolve({
@@ -141,14 +144,6 @@ describe('FleetAuthzRouter', () => {
path: '/api/fleet/test',
fleetAuthz: { fleet: { setup: true } },
};
- it('allow users with superuser role', async () => {
- expect(
- await runTest({
- security: { roles: ['superuser'] },
- routeConfig,
- })
- ).toEqual('ok');
- });
it('allow users with fleet-setup role', async () => {
mockCheckPrivileges.mockImplementation(
@@ -173,46 +168,12 @@ describe('FleetAuthzRouter', () => {
});
});
- describe('with superuser privileges', () => {
- const routeConfig = {
- path: '/api/fleet/test',
- fleetAuthz: { integrations: { uploadPackages: true } },
- };
- it('allow users with superuser role', async () => {
- expect(
- await runTest({
- security: { roles: ['superuser'] },
- routeConfig,
- })
- ).toEqual('ok');
- });
-
- it('do not allow users without superuser role', async () => {
- mockCheckPrivileges.mockImplementation(getCheckPrivilegesMockedImplementation([]));
- expect(
- await runTest({
- security: { checkPrivilegesDynamically: mockCheckPrivileges },
- routeConfig,
- })
- ).toEqual('forbidden');
- });
- });
-
describe('with fleet role', () => {
const routeConfig = {
path: '/api/fleet/test',
fleetAuthz: { integrations: { readPackageInfo: true } },
};
- it('allow users with superuser role', async () => {
- expect(
- await runTest({
- security: { roles: ['superuser'] },
- routeConfig,
- })
- ).toEqual('ok');
- });
-
it('allow users with all required fleet authz role', async () => {
mockCheckPrivileges.mockImplementation(
getCheckPrivilegesMockedImplementation(['api:integrations-read'])
diff --git a/x-pack/plugins/fleet/server/routes/security.ts b/x-pack/plugins/fleet/server/routes/security.ts
index 41776853cde4..cdf997a4d0ea 100644
--- a/x-pack/plugins/fleet/server/routes/security.ts
+++ b/x-pack/plugins/fleet/server/routes/security.ts
@@ -16,10 +16,11 @@ import type {
} from 'src/core/server';
import type { FleetAuthz } from '../../common';
-import { calculateAuthz } from '../../common';
+import { calculateAuthz, INTEGRATIONS_PLUGIN_ID } from '../../common';
import { appContextService } from '../services';
import type { FleetRequestHandlerContext } from '../types';
+import { PLUGIN_ID } from '../constants';
function checkSecurityEnabled() {
return appContextService.getSecurityLicense().isEnabled();
@@ -44,60 +45,47 @@ export function checkSuperuser(req: KibanaRequest) {
return true;
}
-async function checkFleetSetupPrivilege(req: KibanaRequest) {
- if (!checkSecurityEnabled()) {
- return false;
- }
-
- const security = appContextService.getSecurity();
-
- if (security.authz.mode.useRbacForRequest(req)) {
- const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req);
- const { hasAllRequested } = await checkPrivileges(
- { kibana: [security.authz.actions.api.get('fleet-setup')] },
- { requireLoginAction: false } // exclude login access requirement
- );
- return !!hasAllRequested;
- }
-
- return true;
+function getAuthorizationFromPrivileges(
+ kibanaPrivileges: Array<{
+ resource?: string;
+ privilege: string;
+ authorized: boolean;
+ }>,
+ searchPrivilege: string
+) {
+ const privilege = kibanaPrivileges.find((p) => p.privilege.includes(searchPrivilege));
+ return privilege ? privilege.authorized : false;
}
export async function getAuthzFromRequest(req: KibanaRequest): Promise {
const security = appContextService.getSecurity();
if (security.authz.mode.useRbacForRequest(req)) {
- if (checkSuperuser(req)) {
- // Superusers get access to everything
- // Once we implement Kibana RBAC, remove this and use `checkPrivileges` exclusively
- return calculateAuthz({
- fleet: { all: true, setup: true },
- integrations: { all: true, read: true },
- isSuperuser: true,
- });
- } else if (await checkFleetSetupPrivilege(req)) {
- // fleet-setup privilege only gets access to setup actions
- return calculateAuthz({
- fleet: { all: false, setup: true },
- integrations: { all: false, read: false },
- isSuperuser: false,
- });
- } else {
- // All other users only get access to read integrations if they have the read privilege
- const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req);
- const { privileges } = await checkPrivileges({
- kibana: [security.authz.actions.api.get('integrations-read')],
- });
+ const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req);
+ const { privileges } = await checkPrivileges({
+ kibana: [
+ security.authz.actions.api.get(`${PLUGIN_ID}-all`),
+ security.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-all`),
+ security.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-read`),
+ security.authz.actions.api.get('fleet-setup'),
+ ],
+ });
+ const fleetAllAuth = getAuthorizationFromPrivileges(privileges.kibana, `${PLUGIN_ID}-all`);
+ const intAllAuth = getAuthorizationFromPrivileges(
+ privileges.kibana,
+ `${INTEGRATIONS_PLUGIN_ID}-all`
+ );
+ const intReadAuth = getAuthorizationFromPrivileges(
+ privileges.kibana,
+ `${INTEGRATIONS_PLUGIN_ID}-read`
+ );
+ const fleetSetupAuth = getAuthorizationFromPrivileges(privileges.kibana, 'fleet-setup');
- const [intRead] = privileges.kibana;
-
- // Once we implement Kibana RBAC, use `checkPrivileges` for all privileges instead of only integrations.read
- return calculateAuthz({
- fleet: { all: false, setup: false },
- integrations: { all: false, read: intRead.authorized },
- isSuperuser: false,
- });
- }
+ return calculateAuthz({
+ fleet: { all: fleetAllAuth, setup: fleetSetupAuth },
+ integrations: { all: intAllAuth, read: intReadAuth },
+ isSuperuser: checkSuperuser(req),
+ });
}
return calculateAuthz({
diff --git a/x-pack/plugins/fleet/server/routes/settings/index.ts b/x-pack/plugins/fleet/server/routes/settings/index.ts
index 6b86ce17827d..a6203de097c7 100644
--- a/x-pack/plugins/fleet/server/routes/settings/index.ts
+++ b/x-pack/plugins/fleet/server/routes/settings/index.ts
@@ -5,17 +5,17 @@
* 2.0.
*/
-import type { RequestHandler } from 'src/core/server';
import type { TypeOf } from '@kbn/config-schema';
import { SETTINGS_API_ROUTES } from '../../constants';
+import type { FleetRequestHandler } from '../../types';
import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types';
import { defaultIngestErrorHandler } from '../../errors';
import { settingsService, agentPolicyService, appContextService } from '../../services';
import type { FleetAuthzRouter } from '../security';
-export const getSettingsHandler: RequestHandler = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
+export const getSettingsHandler: FleetRequestHandler = async (context, request, response) => {
+ const soClient = context.fleet.epm.internalSoClient;
try {
const settings = await settingsService.getSettings(soClient);
@@ -26,7 +26,7 @@ export const getSettingsHandler: RequestHandler = async (context, request, respo
} catch (error) {
if (error.isBoom && error.output.statusCode === 404) {
return response.notFound({
- body: { message: `Setings not found` },
+ body: { message: `Settings not found` },
});
}
@@ -34,12 +34,12 @@ export const getSettingsHandler: RequestHandler = async (context, request, respo
}
};
-export const putSettingsHandler: RequestHandler<
+export const putSettingsHandler: FleetRequestHandler<
undefined,
undefined,
TypeOf
> = async (context, request, response) => {
- const soClient = context.core.savedObjects.client;
+ const soClient = context.fleet.epm.internalSoClient;
const esClient = context.core.elasticsearch.client.asInternalUser;
const user = await appContextService.getSecurity()?.authc.getCurrentUser(request);
@@ -55,7 +55,7 @@ export const putSettingsHandler: RequestHandler<
} catch (error) {
if (error.isBoom && error.output.statusCode === 404) {
return response.notFound({
- body: { message: `Setings not found` },
+ body: { message: `Settings not found` },
});
}
diff --git a/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts b/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts
index 7dd61be1ab1b..7ccc37c9d986 100644
--- a/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts
+++ b/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts
@@ -13,14 +13,15 @@ import type { ElasticsearchClient } from '../../../../../../src/core/server';
import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { FleetUnauthorizedError } from '../../errors';
-import { checkSuperuser } from '../../routes/security';
+import { getAuthzFromRequest } from '../../routes/security';
+import type { FleetAuthz } from '../../../common';
import type { AgentClient } from './agent_service';
import { AgentServiceImpl } from './agent_service';
import { getAgentsByKuery, getAgentById } from './crud';
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
-const mockCheckSuperuser = checkSuperuser as jest.Mock;
+const mockGetAuthzFromRequest = getAuthzFromRequest as jest.Mock>;
const mockGetAgentsByKuery = getAgentsByKuery as jest.Mock;
const mockGetAgentById = getAgentById as jest.Mock;
const mockGetAgentStatusById = getAgentStatusById as jest.Mock;
@@ -37,7 +38,30 @@ describe('AgentService', () => {
elasticsearchServiceMock.createElasticsearchClient()
).asScoped(httpServerMock.createKibanaRequest());
- beforeEach(() => mockCheckSuperuser.mockReturnValue(false));
+ beforeEach(() =>
+ mockGetAuthzFromRequest.mockReturnValue(
+ Promise.resolve({
+ fleet: {
+ all: false,
+ setup: false,
+ readEnrollmentTokens: false,
+ readAgentPolicies: false,
+ },
+ integrations: {
+ readPackageInfo: false,
+ readInstalledPackages: false,
+ installPackages: false,
+ upgradePackages: false,
+ uploadPackages: false,
+ removePackages: false,
+ readPackageSettings: false,
+ writePackageSettings: false,
+ readIntegrationPolicies: false,
+ writeIntegrationPolicies: false,
+ },
+ })
+ )
+ );
it('rejects on listAgents', async () => {
await expect(agentClient.listAgents({ showInactive: true })).rejects.toThrowError(
@@ -78,7 +102,30 @@ describe('AgentService', () => {
httpServerMock.createKibanaRequest()
);
- beforeEach(() => mockCheckSuperuser.mockReturnValue(true));
+ beforeEach(() =>
+ mockGetAuthzFromRequest.mockReturnValue(
+ Promise.resolve({
+ fleet: {
+ all: true,
+ setup: true,
+ readEnrollmentTokens: true,
+ readAgentPolicies: true,
+ },
+ integrations: {
+ readPackageInfo: true,
+ readInstalledPackages: true,
+ installPackages: true,
+ upgradePackages: true,
+ uploadPackages: true,
+ removePackages: true,
+ readPackageSettings: true,
+ writePackageSettings: true,
+ readIntegrationPolicies: true,
+ writeIntegrationPolicies: true,
+ },
+ })
+ )
+ );
expectApisToCallServicesSuccessfully(mockEsClient, agentClient);
});
diff --git a/x-pack/plugins/fleet/server/services/agents/agent_service.ts b/x-pack/plugins/fleet/server/services/agents/agent_service.ts
index 0286c29cba0c..d23f3b0c88ad 100644
--- a/x-pack/plugins/fleet/server/services/agents/agent_service.ts
+++ b/x-pack/plugins/fleet/server/services/agents/agent_service.ts
@@ -12,7 +12,7 @@ import type { ElasticsearchClient, KibanaRequest } from 'kibana/server';
import type { AgentStatus, ListWithKuery } from '../../types';
import type { Agent, GetAgentStatusResponse } from '../../../common';
-import { checkSuperuser } from '../../routes/security';
+import { getAuthzFromRequest } from '../../routes/security';
import { FleetUnauthorizedError } from '../../errors';
@@ -123,8 +123,9 @@ export class AgentServiceImpl implements AgentService {
constructor(private readonly internalEsClient: ElasticsearchClient) {}
public asScoped(req: KibanaRequest) {
- const preflightCheck = () => {
- if (!checkSuperuser(req)) {
+ const preflightCheck = async () => {
+ const authz = await getAuthzFromRequest(req);
+ if (!authz.fleet.all) {
throw new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
);
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/check_permissions.ts b/x-pack/plugins/fleet/server/types/rest_spec/check_permissions.ts
new file mode 100644
index 000000000000..31850308f9a1
--- /dev/null
+++ b/x-pack/plugins/fleet/server/types/rest_spec/check_permissions.ts
@@ -0,0 +1,14 @@
+/*
+ * 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 { schema } from '@kbn/config-schema';
+
+export const CheckPermissionsRequestSchema = {
+ query: schema.object({
+ fleetServerSetup: schema.maybe(schema.boolean()),
+ }),
+};
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/index.ts b/x-pack/plugins/fleet/server/types/rest_spec/index.ts
index badf02e2e624..fe5dae4e39ed 100644
--- a/x-pack/plugins/fleet/server/types/rest_spec/index.ts
+++ b/x-pack/plugins/fleet/server/types/rest_spec/index.ts
@@ -15,3 +15,4 @@ export * from './output';
export * from './preconfiguration';
export * from './settings';
export * from './setup';
+export * from './check_permissions';
diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts
index 4daef6cca45b..c410de5d696f 100644
--- a/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/endpoint/use_endpoint_privileges.test.ts
@@ -34,7 +34,6 @@ describe('When using useEndpointPrivileges hook', () => {
let authenticatedUser: AuthenticatedUser;
let result: RenderResult;
let unmount: ReturnType['unmount'];
- let releaseFleetAuthz: () => void;
let render: () => RenderHookResult;
beforeEach(() => {
@@ -46,14 +45,6 @@ describe('When using useEndpointPrivileges hook', () => {
licenseServiceMock.isPlatinumPlus.mockReturnValue(true);
- // Add a daly to fleet service that provides authz information
- const fleetAuthz = useKibana().services.fleet!.authz;
-
- // Add a delay to the fleet Authz promise to test out the `loading` property
- useKibana().services.fleet!.authz = new Promise((resolve) => {
- releaseFleetAuthz = () => resolve(fleetAuthz);
- });
-
render = () => {
const hookRenderResponse = renderHook(() => useEndpointPrivileges());
({ result, unmount } = hookRenderResponse);
@@ -78,7 +69,6 @@ describe('When using useEndpointPrivileges hook', () => {
// Release the API response
await act(async () => {
- releaseFleetAuthz();
await useKibana().services.fleet!.authz;
});
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index d74febe395c3..142c83916836 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -11653,7 +11653,6 @@
"xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage": "名前が必要です",
"xpack.fleet.packagePolicyValidation.quoteStringErrorMessage": "*や&などの特殊YAML文字で始まる文字列は二重引用符で囲む必要があります。",
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "{fieldName}が必要です",
- "xpack.fleet.permissionDeniedErrorMessage": "Fleet へのアクセスが許可されていません。Fleet には{roleName}権限が必要です。",
"xpack.fleet.permissionDeniedErrorTitle": "パーミッションが拒否されました",
"xpack.fleet.permissionsRequestErrorMessageDescription": "Fleet アクセス権の確認中に問題が発生しました",
"xpack.fleet.permissionsRequestErrorMessageTitle": "アクセス権を確認できません",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index f76fa799e5b0..961c3c02ded5 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -11777,7 +11777,6 @@
"xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage": "“名称”必填",
"xpack.fleet.packagePolicyValidation.quoteStringErrorMessage": "以特殊 YAML 字符(* 或 &)开头的字符串需要使用双引号引起。",
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "“{fieldName}”必填",
- "xpack.fleet.permissionDeniedErrorMessage": "您无权访问 Fleet。Fleet 需要 {roleName} 权限。",
"xpack.fleet.permissionDeniedErrorTitle": "权限被拒绝",
"xpack.fleet.permissionsRequestErrorMessageDescription": "检查 Fleet 权限时遇到问题",
"xpack.fleet.permissionsRequestErrorMessageTitle": "无法检查权限",
diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts
index d5b66c4d6da9..378db8eecb5b 100644
--- a/x-pack/test/api_integration/apis/features/features/features.ts
+++ b/x-pack/test/api_integration/apis/features/features/features.ts
@@ -119,6 +119,7 @@ export default function ({ getService }: FtrProviderContext) {
'siem',
'securitySolutionCases',
'fleet',
+ 'fleetv2',
].sort()
);
});
diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts
index 95076fe01000..667b13b854b7 100644
--- a/x-pack/test/api_integration/apis/security/privileges.ts
+++ b/x-pack/test/api_integration/apis/security/privileges.ts
@@ -29,6 +29,7 @@ export default function ({ getService }: FtrProviderContext) {
canvas: ['all', 'read', 'minimal_all', 'minimal_read'],
maps: ['all', 'read', 'minimal_all', 'minimal_read'],
observabilityCases: ['all', 'read', 'minimal_all', 'minimal_read'],
+ fleetv2: ['all', 'read', 'minimal_all', 'minimal_read'],
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts
index fc3d038c3965..0fcc15e1de6e 100644
--- a/x-pack/test/api_integration/apis/security/privileges_basic.ts
+++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts
@@ -40,6 +40,7 @@ export default function ({ getService }: FtrProviderContext) {
ml: ['all', 'read', 'minimal_all', 'minimal_read'],
siem: ['all', 'read', 'minimal_all', 'minimal_read'],
securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read'],
+ fleetv2: ['all', 'read', 'minimal_all', 'minimal_read'],
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
actions: ['all', 'read', 'minimal_all', 'minimal_read'],