diff --git a/x-pack/plugins/fleet/.storybook/context/application.ts b/x-pack/plugins/fleet/.storybook/context/application.ts index 7503de562427..4cae11dd79c0 100644 --- a/x-pack/plugins/fleet/.storybook/context/application.ts +++ b/x-pack/plugins/fleet/.storybook/context/application.ts @@ -27,7 +27,12 @@ export const getApplication = () => { management: {}, navLinks: {}, fleet: { - write: true, + read: true, + all: true, + }, + fleetv2: { + read: true, + all: true, }, }, applications$: of(applications), diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index 0540607a12a0..c8ae7b76d040 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -68,7 +68,7 @@ export const calculateAuthz = ({ readPackageSettings: fleet.all && integrations.all, writePackageSettings: fleet.all && integrations.all, - readIntegrationPolicies: fleet.all && integrations.all, + readIntegrationPolicies: fleet.all && (integrations.all || integrations.read), writeIntegrationPolicies: fleet.all && integrations.all, }, }); diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index d7954aff70dd..b3a53cd05da4 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -184,7 +184,7 @@ export const settingsRoutesService = { }; export const appRoutesService = { - getCheckPermissionsPath: () => APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN, + getCheckPermissionsPath: (fleetServerSetup?: boolean) => APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN, getRegenerateServiceTokenPath: () => APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN, }; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/app.ts b/x-pack/plugins/fleet/common/types/rest_spec/app.ts index a742c387c14a..90240606f938 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/app.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/app.ts @@ -6,7 +6,7 @@ */ export interface CheckPermissionsResponse { - error?: 'MISSING_SECURITY' | 'MISSING_SUPERUSER_ROLE'; + error?: 'MISSING_SECURITY' | 'MISSING_PRIVILEGES' | 'MISSING_FLEET_SERVER_SETUP_PRIVILEGES'; success: boolean; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 9bf481ecf094..9799561970e4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -29,6 +29,8 @@ import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/com import { PackageInstallProvider } from '../integrations/hooks'; +import { useAuthz } from './hooks'; + import { ConfigContext, FleetStatusProvider, @@ -80,9 +82,9 @@ const PermissionsError: React.FunctionComponent<{ error: string }> = memo(({ err return ; } - if (error === 'MISSING_SUPERUSER_ROLE') { + if (error === 'MISSING_PRIVILEGES') { return ( - + = 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'],