mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[Fleet] Add support for non-superuser access to Fleet and Integrations (#122347)
* [Fleet] Split Fleet and Integration privileges * Update UI when Fleet has All privileges and Integrations have None * Replace remaining superuser checks * Updates to server/plugin * Update getAuthzFromRequest * Update start method in the client side * Fix tests * Fix functional tests * Make changes to the UI based on new privilege system * Further UI changes * Make capabilities accessible to unit tests in createStartServices * Fix failing tests * Fix ts checks * Address most review comments * Introduce hook exposing authz and make UI checks more granular; address rest of comments * Remove capabilities hook * Get rid of useCapabilites * Address review comments * Other fixes * Fix tutorial app privileges * Address code review comments and update privileges naming * Fix i18n failing check * Block fleet server setup UI when user does not have manage_service_account privilege * Minor changes * Use unique i18n id Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Josh Dover <doverfake@elastic.co>
This commit is contained in:
parent
a88d4a8f6b
commit
b12f70800c
54 changed files with 461 additions and 294 deletions
|
@ -27,7 +27,12 @@ export const getApplication = () => {
|
||||||
management: {},
|
management: {},
|
||||||
navLinks: {},
|
navLinks: {},
|
||||||
fleet: {
|
fleet: {
|
||||||
write: true,
|
read: true,
|
||||||
|
all: true,
|
||||||
|
},
|
||||||
|
fleetv2: {
|
||||||
|
read: true,
|
||||||
|
all: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
applications$: of(applications),
|
applications$: of(applications),
|
||||||
|
|
|
@ -68,7 +68,7 @@ export const calculateAuthz = ({
|
||||||
readPackageSettings: fleet.all && integrations.all,
|
readPackageSettings: fleet.all && integrations.all,
|
||||||
writePackageSettings: 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,
|
writeIntegrationPolicies: fleet.all && integrations.all,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -184,7 +184,7 @@ export const settingsRoutesService = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appRoutesService = {
|
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,
|
getRegenerateServiceTokenPath: () => APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface CheckPermissionsResponse {
|
export interface CheckPermissionsResponse {
|
||||||
error?: 'MISSING_SECURITY' | 'MISSING_SUPERUSER_ROLE';
|
error?: 'MISSING_SECURITY' | 'MISSING_PRIVILEGES' | 'MISSING_FLEET_SERVER_SETUP_PRIVILEGES';
|
||||||
success: boolean;
|
success: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/com
|
||||||
|
|
||||||
import { PackageInstallProvider } from '../integrations/hooks';
|
import { PackageInstallProvider } from '../integrations/hooks';
|
||||||
|
|
||||||
|
import { useAuthz } from './hooks';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConfigContext,
|
ConfigContext,
|
||||||
FleetStatusProvider,
|
FleetStatusProvider,
|
||||||
|
@ -80,9 +82,9 @@ const PermissionsError: React.FunctionComponent<{ error: string }> = memo(({ err
|
||||||
return <MissingESRequirementsPage missingRequirements={['security_required', 'api_keys']} />;
|
return <MissingESRequirementsPage missingRequirements={['security_required', 'api_keys']} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error === 'MISSING_SUPERUSER_ROLE') {
|
if (error === 'MISSING_PRIVILEGES') {
|
||||||
return (
|
return (
|
||||||
<Panel>
|
<Panel data-test-subj="missingPrivilegesPrompt">
|
||||||
<EuiEmptyPrompt
|
<EuiEmptyPrompt
|
||||||
iconType="securityApp"
|
iconType="securityApp"
|
||||||
title={
|
title={
|
||||||
|
@ -97,8 +99,11 @@ const PermissionsError: React.FunctionComponent<{ error: string }> = memo(({ err
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.permissionDeniedErrorMessage"
|
id="xpack.fleet.permissionDeniedErrorMessage"
|
||||||
defaultMessage="You are not authorized to access Fleet. Fleet requires {roleName} privileges."
|
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={{ roleName: <EuiCode>superuser</EuiCode> }}
|
values={{
|
||||||
|
roleName1: <EuiCode>"All"</EuiCode>,
|
||||||
|
roleName2: <EuiCode>"Read"</EuiCode>,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
@ -124,7 +129,10 @@ const PermissionsError: React.FunctionComponent<{ error: string }> = memo(({ err
|
||||||
|
|
||||||
export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
|
export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
|
||||||
useBreadcrumbs('base');
|
useBreadcrumbs('base');
|
||||||
const { notifications } = useStartServices();
|
const core = useStartServices();
|
||||||
|
const { notifications } = core;
|
||||||
|
|
||||||
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
|
|
||||||
const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
|
const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
|
||||||
const [permissionsError, setPermissionsError] = useState<string>();
|
const [permissionsError, setPermissionsError] = useState<string>();
|
||||||
|
@ -156,6 +164,9 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!hasFleetAllPrivileges) {
|
||||||
|
setPermissionsError('MISSING_PRIVILEGES');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setInitializationError(err);
|
setInitializationError(err);
|
||||||
}
|
}
|
||||||
|
@ -167,7 +178,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
|
||||||
setPermissionsError('REQUEST_ERROR');
|
setPermissionsError('REQUEST_ERROR');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [notifications.toasts]);
|
}, [notifications.toasts, hasFleetAllPrivileges]);
|
||||||
|
|
||||||
if (isPermissionsLoading || permissionsError) {
|
if (isPermissionsLoading || permissionsError) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
|
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
|
||||||
|
|
||||||
import type { AgentPolicy } from '../../../types';
|
import type { AgentPolicy } from '../../../types';
|
||||||
import { useCapabilities } from '../../../hooks';
|
import { useAuthz } from '../../../hooks';
|
||||||
import { AgentEnrollmentFlyout, ContextMenuActions } from '../../../components';
|
import { AgentEnrollmentFlyout, ContextMenuActions } from '../../../components';
|
||||||
|
|
||||||
import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout';
|
import { AgentPolicyYamlFlyout } from './agent_policy_yaml_flyout';
|
||||||
|
@ -30,7 +30,7 @@ export const AgentPolicyActionMenu = memo<{
|
||||||
enrollmentFlyoutOpenByDefault = false,
|
enrollmentFlyoutOpenByDefault = false,
|
||||||
onCancelEnrollment,
|
onCancelEnrollment,
|
||||||
}) => {
|
}) => {
|
||||||
const hasWriteCapabilities = useCapabilities().write;
|
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||||
const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState<boolean>(false);
|
const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState<boolean>(false);
|
||||||
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState<boolean>(
|
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState<boolean>(
|
||||||
enrollmentFlyoutOpenByDefault
|
enrollmentFlyoutOpenByDefault
|
||||||
|
@ -76,7 +76,6 @@ export const AgentPolicyActionMenu = memo<{
|
||||||
? [viewPolicyItem]
|
? [viewPolicyItem]
|
||||||
: [
|
: [
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
disabled={!hasWriteCapabilities}
|
|
||||||
icon="plusInCircle"
|
icon="plusInCircle"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
|
@ -91,7 +90,7 @@ export const AgentPolicyActionMenu = memo<{
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
viewPolicyItem,
|
viewPolicyItem,
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
disabled={!hasWriteCapabilities}
|
disabled={!canWriteIntegrationPolicies}
|
||||||
icon="copy"
|
icon="copy"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../..
|
||||||
import {
|
import {
|
||||||
useGetAgentPolicies,
|
useGetAgentPolicies,
|
||||||
sendGetOneAgentPolicy,
|
sendGetOneAgentPolicy,
|
||||||
useCapabilities,
|
useAuthz,
|
||||||
useFleetStatus,
|
useFleetStatus,
|
||||||
} from '../../../hooks';
|
} from '../../../hooks';
|
||||||
import { CreateAgentPolicyFlyout } from '../list_page/components';
|
import { CreateAgentPolicyFlyout } from '../list_page/components';
|
||||||
|
@ -63,7 +63,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
|
||||||
const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState<Error>();
|
const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState<Error>();
|
||||||
|
|
||||||
// Create new agent policy flyout state
|
// Create new agent policy flyout state
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
const [isCreateAgentPolicyFlyoutOpen, setIsCreateAgentPolicyFlyoutOpen] =
|
const [isCreateAgentPolicyFlyoutOpen, setIsCreateAgentPolicyFlyoutOpen] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<div>
|
<div>
|
||||||
<EuiLink
|
<EuiLink
|
||||||
disabled={!hasWriteCapabilites}
|
disabled={!hasFleetAllPrivileges}
|
||||||
onClick={() => setIsCreateAgentPolicyFlyoutOpen(true)}
|
onClick={() => setIsCreateAgentPolicyFlyoutOpen(true)}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
|
@ -9,12 +9,12 @@ import React, { memo } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||||
|
|
||||||
import { useCapabilities, useStartServices } from '../../../../../hooks';
|
import { useAuthz, useStartServices } from '../../../../../hooks';
|
||||||
import { pagePathGetters, INTEGRATIONS_PLUGIN_ID } from '../../../../../constants';
|
import { pagePathGetters, INTEGRATIONS_PLUGIN_ID } from '../../../../../constants';
|
||||||
|
|
||||||
export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => {
|
export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => {
|
||||||
const { application } = useStartServices();
|
const { application } = useStartServices();
|
||||||
const hasWriteCapabilities = useCapabilities().write;
|
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiEmptyPrompt
|
<EuiEmptyPrompt
|
||||||
|
@ -35,7 +35,7 @@ export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => {
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<EuiButton
|
<EuiButton
|
||||||
isDisabled={!hasWriteCapabilities}
|
isDisabled={!canWriteIntegrationPolicies}
|
||||||
fill
|
fill
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||||
|
|
|
@ -25,12 +25,7 @@ import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../common';
|
||||||
import { pagePathGetters } from '../../../../../../../constants';
|
import { pagePathGetters } from '../../../../../../../constants';
|
||||||
import type { AgentPolicy, InMemoryPackagePolicy, PackagePolicy } from '../../../../../types';
|
import type { AgentPolicy, InMemoryPackagePolicy, PackagePolicy } from '../../../../../types';
|
||||||
import { PackageIcon, PackagePolicyActionsMenu } from '../../../../../components';
|
import { PackageIcon, PackagePolicyActionsMenu } from '../../../../../components';
|
||||||
import {
|
import { useAuthz, useLink, usePackageInstallations, useStartServices } from '../../../../../hooks';
|
||||||
useCapabilities,
|
|
||||||
useLink,
|
|
||||||
usePackageInstallations,
|
|
||||||
useStartServices,
|
|
||||||
} from '../../../../../hooks';
|
|
||||||
import { pkgKeyFromPackageInfo } from '../../../../../services';
|
import { pkgKeyFromPackageInfo } from '../../../../../services';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -55,7 +50,8 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { application } = useStartServices();
|
const { application } = useStartServices();
|
||||||
const hasWriteCapabilities = useCapabilities().write;
|
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||||
|
const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies;
|
||||||
const { updatableIntegrations } = usePackageInstallations();
|
const { updatableIntegrations } = usePackageInstallations();
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
|
|
||||||
|
@ -109,7 +105,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
render: (value: string, packagePolicy: InMemoryPackagePolicy) => (
|
render: (value: string, packagePolicy: InMemoryPackagePolicy) => (
|
||||||
<EuiLink
|
<EuiLink
|
||||||
title={value}
|
title={value}
|
||||||
{...(hasWriteCapabilities
|
{...(canReadIntegrationPolicies
|
||||||
? {
|
? {
|
||||||
href: getHref('edit_integration', {
|
href: getHref('edit_integration', {
|
||||||
policyId: agentPolicy.id,
|
policyId: agentPolicy.id,
|
||||||
|
@ -144,7 +140,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
render(packageTitle: string, packagePolicy: InMemoryPackagePolicy) {
|
render(packageTitle: string, packagePolicy: InMemoryPackagePolicy) {
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem data-test-subj="PackagePoliciesTableLink" grow={false}>
|
||||||
<EuiLink
|
<EuiLink
|
||||||
href={
|
href={
|
||||||
packagePolicy.package &&
|
packagePolicy.package &&
|
||||||
|
@ -195,6 +191,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
<EuiButton
|
<EuiButton
|
||||||
size="s"
|
size="s"
|
||||||
minWidth="0"
|
minWidth="0"
|
||||||
|
isDisabled={!canWriteIntegrationPolicies}
|
||||||
href={`${getHref('upgrade_package_policy', {
|
href={`${getHref('upgrade_package_policy', {
|
||||||
policyId: agentPolicy.id,
|
policyId: agentPolicy.id,
|
||||||
packagePolicyId: packagePolicy.id,
|
packagePolicyId: packagePolicy.id,
|
||||||
|
@ -231,7 +228,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
render: (packagePolicy: InMemoryPackagePolicy) => {
|
render: (packagePolicy: InMemoryPackagePolicy) => {
|
||||||
return (
|
return canWriteIntegrationPolicies ? (
|
||||||
<PackagePolicyActionsMenu
|
<PackagePolicyActionsMenu
|
||||||
agentPolicy={agentPolicy}
|
agentPolicy={agentPolicy}
|
||||||
packagePolicy={packagePolicy}
|
packagePolicy={packagePolicy}
|
||||||
|
@ -240,13 +237,15 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
packagePolicyId: packagePolicy.id,
|
packagePolicyId: packagePolicy.id,
|
||||||
})}?from=fleet-policy-list`}
|
})}?from=fleet-policy-list`}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[agentPolicy, getHref, hasWriteCapabilities]
|
[agentPolicy, getHref, canWriteIntegrationPolicies, canReadIntegrationPolicies]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -268,7 +267,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
||||||
<EuiButton
|
<EuiButton
|
||||||
key="addPackagePolicyButton"
|
key="addPackagePolicyButton"
|
||||||
fill
|
fill
|
||||||
isDisabled={!hasWriteCapabilities}
|
isDisabled={!canWriteIntegrationPolicies}
|
||||||
iconType="plusInCircle"
|
iconType="plusInCircle"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import type { AgentPolicy } from '../../../../../types';
|
||||||
import {
|
import {
|
||||||
useLink,
|
useLink,
|
||||||
useStartServices,
|
useStartServices,
|
||||||
useCapabilities,
|
useAuthz,
|
||||||
sendUpdateAgentPolicy,
|
sendUpdateAgentPolicy,
|
||||||
useConfig,
|
useConfig,
|
||||||
sendGetAgentStatus,
|
sendGetAgentStatus,
|
||||||
|
@ -51,7 +51,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
|
||||||
} = useConfig();
|
} = useConfig();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { getPath } = useLink();
|
const { getPath } = useLink();
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
const refreshAgentPolicy = useAgentPolicyRefresh();
|
const refreshAgentPolicy = useAgentPolicyRefresh();
|
||||||
const [agentPolicy, setAgentPolicy] = useState<AgentPolicy>({
|
const [agentPolicy, setAgentPolicy] = useState<AgentPolicy>({
|
||||||
...originalAgentPolicy,
|
...originalAgentPolicy,
|
||||||
|
@ -186,7 +186,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
!hasWriteCapabilites || isLoading || Object.keys(validation).length > 0
|
!hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0
|
||||||
}
|
}
|
||||||
iconType="save"
|
iconType="save"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { safeLoad } from 'js-yaml';
|
import { safeLoad } from 'js-yaml';
|
||||||
import {
|
import {
|
||||||
EuiButtonEmpty,
|
EuiButtonEmpty,
|
||||||
EuiButton,
|
|
||||||
EuiBottomBar,
|
EuiBottomBar,
|
||||||
EuiCallOut,
|
EuiCallOut,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
|
@ -42,6 +41,7 @@ import {
|
||||||
sendGetOnePackagePolicy,
|
sendGetOnePackagePolicy,
|
||||||
sendGetPackageInfoByKey,
|
sendGetPackageInfoByKey,
|
||||||
sendUpgradePackagePolicyDryRun,
|
sendUpgradePackagePolicyDryRun,
|
||||||
|
useAuthz,
|
||||||
} from '../../../hooks';
|
} from '../../../hooks';
|
||||||
import {
|
import {
|
||||||
useBreadcrumbs as useIntegrationsBreadcrumbs,
|
useBreadcrumbs as useIntegrationsBreadcrumbs,
|
||||||
|
@ -65,6 +65,8 @@ import type {
|
||||||
import type { PackagePolicyEditExtensionComponentProps } from '../../../types';
|
import type { PackagePolicyEditExtensionComponentProps } from '../../../types';
|
||||||
import { pkgKeyFromPackageInfo, storedPackagePoliciesToAgentInputs } from '../../../services';
|
import { pkgKeyFromPackageInfo, storedPackagePoliciesToAgentInputs } from '../../../services';
|
||||||
|
|
||||||
|
import { EuiButtonWithTooltip } from '../../../../integrations/sections/epm/screens/detail';
|
||||||
|
|
||||||
import { hasUpgradeAvailable } from './utils';
|
import { hasUpgradeAvailable } from './utils';
|
||||||
|
|
||||||
export const EditPackagePolicyPage = memo(() => {
|
export const EditPackagePolicyPage = memo(() => {
|
||||||
|
@ -123,6 +125,8 @@ export const EditPackagePolicyForm = memo<{
|
||||||
|
|
||||||
const [isUpgrade, setIsUpgrade] = useState<boolean>(false);
|
const [isUpgrade, setIsUpgrade] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (forceUpgrade) {
|
if (forceUpgrade) {
|
||||||
setIsUpgrade(true);
|
setIsUpgrade(true);
|
||||||
|
@ -625,11 +629,27 @@ export const EditPackagePolicyForm = memo<{
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButton
|
<EuiButtonWithTooltip
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
isLoading={formState === 'LOADING'}
|
isLoading={formState === 'LOADING'}
|
||||||
// Allow to save only if the package policy is upgraded or had been edited
|
// Allow to save only if the package policy is upgraded or had been edited
|
||||||
disabled={formState !== 'VALID' || (!isEdited && !isUpgrade)}
|
isDisabled={
|
||||||
|
!canWriteIntegrationPolicies ||
|
||||||
|
formState !== 'VALID' ||
|
||||||
|
(!isEdited && !isUpgrade)
|
||||||
|
}
|
||||||
|
tooltip={
|
||||||
|
!canWriteIntegrationPolicies
|
||||||
|
? {
|
||||||
|
content: (
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.fleet.agentPolicy.saveIntegrationTooltip"
|
||||||
|
defaultMessage="To save the integration policy, you must have security enabled and have the All privilege for Integrations. Contact your administrator."
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
iconType="save"
|
iconType="save"
|
||||||
color="primary"
|
color="primary"
|
||||||
fill
|
fill
|
||||||
|
@ -646,7 +666,7 @@ export const EditPackagePolicyForm = memo<{
|
||||||
defaultMessage="Save integration"
|
defaultMessage="Save integration"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</EuiButton>
|
</EuiButtonWithTooltip>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
|
|
||||||
import { dataTypes } from '../../../../../../../common';
|
import { dataTypes } from '../../../../../../../common';
|
||||||
import type { NewAgentPolicy, AgentPolicy } from '../../../../types';
|
import type { NewAgentPolicy, AgentPolicy } from '../../../../types';
|
||||||
import { useCapabilities, useStartServices, sendCreateAgentPolicy } from '../../../../hooks';
|
import { useAuthz, useStartServices, sendCreateAgentPolicy } from '../../../../hooks';
|
||||||
import { AgentPolicyForm, agentPolicyFormValidation } from '../../components';
|
import { AgentPolicyForm, agentPolicyFormValidation } from '../../components';
|
||||||
|
|
||||||
const FlyoutWithHigherZIndex = styled(EuiFlyout)`
|
const FlyoutWithHigherZIndex = styled(EuiFlyout)`
|
||||||
|
@ -43,7 +43,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent<Props> = ({
|
||||||
...restOfProps
|
...restOfProps
|
||||||
}) => {
|
}) => {
|
||||||
const { notifications } = useStartServices();
|
const { notifications } = useStartServices();
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
const [agentPolicy, setAgentPolicy] = useState<NewAgentPolicy>({
|
const [agentPolicy, setAgentPolicy] = useState<NewAgentPolicy>({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
@ -115,7 +115,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent<Props> = ({
|
||||||
<EuiButton
|
<EuiButton
|
||||||
fill
|
fill
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={!hasWriteCapabilites || isLoading || Object.keys(validation).length > 0}
|
isDisabled={!hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { useHistory } from 'react-router-dom';
|
||||||
import type { AgentPolicy } from '../../../types';
|
import type { AgentPolicy } from '../../../types';
|
||||||
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants';
|
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants';
|
||||||
import {
|
import {
|
||||||
useCapabilities,
|
useAuthz,
|
||||||
useGetAgentPolicies,
|
useGetAgentPolicies,
|
||||||
usePagination,
|
usePagination,
|
||||||
useSorting,
|
useSorting,
|
||||||
|
@ -42,7 +42,8 @@ import { CreateAgentPolicyFlyout } from './components';
|
||||||
export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
||||||
useBreadcrumbs('policies_list');
|
useBreadcrumbs('policies_list');
|
||||||
const { getPath } = useLink();
|
const { getPath } = useLink();
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
agents: { enabled: isFleetEnabled },
|
agents: { enabled: isFleetEnabled },
|
||||||
} = useConfig();
|
} = useConfig();
|
||||||
|
@ -148,6 +149,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
||||||
packagePolicies ? packagePolicies.length : 0,
|
packagePolicies ? packagePolicies.length : 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
field: 'actions',
|
||||||
name: i18n.translate('xpack.fleet.agentPolicyList.actionsColumnTitle', {
|
name: i18n.translate('xpack.fleet.agentPolicyList.actionsColumnTitle', {
|
||||||
defaultMessage: 'Actions',
|
defaultMessage: 'Actions',
|
||||||
}),
|
}),
|
||||||
|
@ -177,7 +179,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
||||||
<EuiButton
|
<EuiButton
|
||||||
fill
|
fill
|
||||||
iconType="plusInCircle"
|
iconType="plusInCircle"
|
||||||
isDisabled={!hasWriteCapabilites}
|
isDisabled={!hasFleetAllPrivileges}
|
||||||
onClick={() => setIsCreateAgentPolicyFlyoutOpen(true)}
|
onClick={() => setIsCreateAgentPolicyFlyoutOpen(true)}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -186,7 +188,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
||||||
/>
|
/>
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
),
|
),
|
||||||
[hasWriteCapabilites, setIsCreateAgentPolicyFlyoutOpen]
|
[hasFleetAllPrivileges, setIsCreateAgentPolicyFlyoutOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
const emptyPrompt = useMemo(
|
const emptyPrompt = useMemo(
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { EuiPortal, EuiContextMenuItem } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
|
||||||
import type { Agent, AgentPolicy, PackagePolicy } from '../../../../types';
|
import type { Agent, AgentPolicy, PackagePolicy } from '../../../../types';
|
||||||
import { useCapabilities, useKibanaVersion } from '../../../../hooks';
|
import { useAuthz, useKibanaVersion } from '../../../../hooks';
|
||||||
import { ContextMenuActions } from '../../../../components';
|
import { ContextMenuActions } from '../../../../components';
|
||||||
import {
|
import {
|
||||||
AgentUnenrollAgentModal,
|
AgentUnenrollAgentModal,
|
||||||
|
@ -27,7 +27,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
||||||
assignFlyoutOpenByDefault?: boolean;
|
assignFlyoutOpenByDefault?: boolean;
|
||||||
onCancelReassign?: () => void;
|
onCancelReassign?: () => void;
|
||||||
}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign, agentPolicy }) => {
|
}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign, agentPolicy }) => {
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
const kibanaVersion = useKibanaVersion();
|
const kibanaVersion = useKibanaVersion();
|
||||||
const refreshAgent = useAgentRefresh();
|
const refreshAgent = useAgentRefresh();
|
||||||
const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault);
|
const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault);
|
||||||
|
@ -110,7 +110,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
icon="cross"
|
icon="cross"
|
||||||
disabled={!hasWriteCapabilites || !agent.active}
|
disabled={!hasFleetAllPrivileges || !agent.active}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsUnenrollModalOpen(true);
|
setIsUnenrollModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -139,6 +139,7 @@ export const AgentDetailsIntegration: React.FunctionComponent<{
|
||||||
<EuiFlexItem className="eui-textTruncate">
|
<EuiFlexItem className="eui-textTruncate">
|
||||||
<EuiLink
|
<EuiLink
|
||||||
className="eui-textTruncate"
|
className="eui-textTruncate"
|
||||||
|
data-test-subj="agentPolicyDetailsLink"
|
||||||
href={getHref('edit_integration', {
|
href={getHref('edit_integration', {
|
||||||
policyId: agentPolicy.id,
|
policyId: agentPolicy.id,
|
||||||
packagePolicyId: packagePolicy.id,
|
packagePolicyId: packagePolicy.id,
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';
|
||||||
import type { Agent, AgentPolicy, PackagePolicy, SimplifiedAgentStatus } from '../../../types';
|
import type { Agent, AgentPolicy, PackagePolicy, SimplifiedAgentStatus } from '../../../types';
|
||||||
import {
|
import {
|
||||||
usePagination,
|
usePagination,
|
||||||
useCapabilities,
|
useAuthz,
|
||||||
useGetAgentPolicies,
|
useGetAgentPolicies,
|
||||||
sendGetAgents,
|
sendGetAgents,
|
||||||
sendGetAgentStatus,
|
sendGetAgentStatus,
|
||||||
|
@ -68,7 +68,7 @@ const RowActions = React.memo<{
|
||||||
onUpgradeClick: () => void;
|
onUpgradeClick: () => void;
|
||||||
}>(({ agent, agentPolicy, refresh, onReassignClick, onUnenrollClick, onUpgradeClick }) => {
|
}>(({ agent, agentPolicy, refresh, onReassignClick, onUnenrollClick, onUpgradeClick }) => {
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
|
|
||||||
const isUnenrolling = agent.status === 'unenrolling';
|
const isUnenrolling = agent.status === 'unenrolling';
|
||||||
const kibanaVersion = useKibanaVersion();
|
const kibanaVersion = useKibanaVersion();
|
||||||
|
@ -99,7 +99,7 @@ const RowActions = React.memo<{
|
||||||
/>
|
/>
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
disabled={!hasWriteCapabilites || !agent.active}
|
disabled={!hasFleetAllPrivileges || !agent.active}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onUnenrollClick();
|
onUnenrollClick();
|
||||||
|
@ -152,7 +152,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
||||||
useBreadcrumbs('agent_list');
|
useBreadcrumbs('agent_list');
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || '';
|
const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || '';
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
const isGoldPlus = useLicense().isGoldPlus();
|
const isGoldPlus = useLicense().isGoldPlus();
|
||||||
const kibanaVersion = useKibanaVersion();
|
const kibanaVersion = useKibanaVersion();
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
||||||
</h2>
|
</h2>
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
hasWriteCapabilites ? (
|
hasFleetAllPrivileges ? (
|
||||||
<EuiButton
|
<EuiButton
|
||||||
fill
|
fill
|
||||||
iconType="plusInCircle"
|
iconType="plusInCircle"
|
||||||
|
|
|
@ -5,11 +5,15 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { useStartServices } from '../../../hooks';
|
import { useStartServices, sendGetPermissionsCheck } from '../../../hooks';
|
||||||
|
|
||||||
|
import { FleetServerMissingPrivileges } from '../../agents/components/fleet_server_callouts';
|
||||||
|
|
||||||
|
import { Loading } from '../../../components';
|
||||||
|
|
||||||
import { CloudInstructions, OnPremInstructions } from './components';
|
import { CloudInstructions, OnPremInstructions } from './components';
|
||||||
|
|
||||||
|
@ -27,6 +31,29 @@ export const FleetServerRequirementPage = () => {
|
||||||
const startService = useStartServices();
|
const startService = useStartServices();
|
||||||
const deploymentUrl = startService.cloud?.deploymentUrl;
|
const deploymentUrl = startService.cloud?.deploymentUrl;
|
||||||
|
|
||||||
|
const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
|
||||||
|
const [permissionsError, setPermissionsError] = useState<string>();
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
|
@ -38,6 +65,10 @@ export const FleetServerRequirementPage = () => {
|
||||||
<FlexItemWithMinWidth grow={false}>
|
<FlexItemWithMinWidth grow={false}>
|
||||||
{deploymentUrl ? (
|
{deploymentUrl ? (
|
||||||
<CloudInstructions deploymentUrl={deploymentUrl} />
|
<CloudInstructions deploymentUrl={deploymentUrl} />
|
||||||
|
) : isPermissionsLoading ? (
|
||||||
|
<Loading />
|
||||||
|
) : permissionsError ? (
|
||||||
|
<FleetServerMissingPrivileges />
|
||||||
) : (
|
) : (
|
||||||
<OnPremInstructions />
|
<OnPremInstructions />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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 (
|
||||||
|
<Panel data-test-subj="fleetServerMissingPrivilegesPrompt">
|
||||||
|
<EuiEmptyPrompt
|
||||||
|
iconType="securityApp"
|
||||||
|
title={
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle"
|
||||||
|
defaultMessage="Permission denied"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
body={
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage"
|
||||||
|
defaultMessage="Fleet Server needs to be set up. This requires the {roleName} cluster privilege. Contact your administrator."
|
||||||
|
values={{
|
||||||
|
roleName: <EuiCode>"manage_service_account"</EuiCode>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
|
@ -8,3 +8,4 @@
|
||||||
export * from './fleet_server_cloud_unhealthy_callout';
|
export * from './fleet_server_cloud_unhealthy_callout';
|
||||||
export * from './fleet_server_on_prem_unhealthy_callout';
|
export * from './fleet_server_on_prem_unhealthy_callout';
|
||||||
export * from './fleet_server_on_prem_required_callout';
|
export * from './fleet_server_on_prem_required_callout';
|
||||||
|
export * from './fleet_server_missing_privileges';
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const NoAccessPage = injectI18n(({ intl }) => (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.noAccess.accessDeniedDescription"
|
id="xpack.fleet.noAccess.accessDeniedDescription"
|
||||||
defaultMessage="You are not authorized to access Elastic Fleet. To use Elastic Fleet,
|
defaultMessage="You are not authorized to access Elastic Fleet. To use Elastic Fleet,
|
||||||
you need a user role that contains read or all permissions for this application."
|
you need a user role that contains All permissions for this application."
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</NoDataLayout>
|
</NoDataLayout>
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
useConfig,
|
useConfig,
|
||||||
useFleetStatus,
|
useFleetStatus,
|
||||||
useBreadcrumbs,
|
useBreadcrumbs,
|
||||||
useCapabilities,
|
useAuthz,
|
||||||
useGetSettings,
|
useGetSettings,
|
||||||
useGetAgentPolicies,
|
useGetAgentPolicies,
|
||||||
} from '../../hooks';
|
} from '../../hooks';
|
||||||
|
@ -32,7 +32,7 @@ export const AgentsApp: React.FunctionComponent = () => {
|
||||||
useBreadcrumbs('agent_list');
|
useBreadcrumbs('agent_list');
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { agents } = useConfig();
|
const { agents } = useConfig();
|
||||||
const capabilities = useCapabilities();
|
const hasFleetAllPrivileges = useAuthz().fleet.all;
|
||||||
|
|
||||||
const agentPoliciesRequest = useGetAgentPolicies({
|
const agentPoliciesRequest = useGetAgentPolicies({
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -93,7 +93,7 @@ export const AgentsApp: React.FunctionComponent = () => {
|
||||||
) {
|
) {
|
||||||
return <MissingESRequirementsPage missingRequirements={fleetStatus.missingRequirements} />;
|
return <MissingESRequirementsPage missingRequirements={fleetStatus.missingRequirements} />;
|
||||||
}
|
}
|
||||||
if (!capabilities.read) {
|
if (!hasFleetAllPrivileges) {
|
||||||
return <NoAccessPage />;
|
return <NoAccessPage />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ describe('when on integration detail', () => {
|
||||||
expect(renderResult.queryByTestId('agentPolicyCount')).toBeNull();
|
expect(renderResult.queryByTestId('agentPolicyCount')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT the Policies tab', async () => {
|
it('should NOT display the Policies tab', async () => {
|
||||||
await mockedApi.waitForApi();
|
await mockedApi.waitForApi();
|
||||||
expect(renderResult.queryByTestId('tab-policies')).toBeNull();
|
expect(renderResult.queryByTestId('tab-policies')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,7 @@ import {
|
||||||
useUIExtension,
|
useUIExtension,
|
||||||
useBreadcrumbs,
|
useBreadcrumbs,
|
||||||
useStartServices,
|
useStartServices,
|
||||||
|
useAuthz,
|
||||||
usePermissionCheck,
|
usePermissionCheck,
|
||||||
} from '../../../../hooks';
|
} from '../../../../hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -43,12 +44,7 @@ import {
|
||||||
INTEGRATIONS_ROUTING_PATHS,
|
INTEGRATIONS_ROUTING_PATHS,
|
||||||
pagePathGetters,
|
pagePathGetters,
|
||||||
} from '../../../../constants';
|
} from '../../../../constants';
|
||||||
import {
|
import { useGetPackageInfoByKey, useLink, useAgentPolicyContext } from '../../../../hooks';
|
||||||
useCapabilities,
|
|
||||||
useGetPackageInfoByKey,
|
|
||||||
useLink,
|
|
||||||
useAgentPolicyContext,
|
|
||||||
} from '../../../../hooks';
|
|
||||||
import { pkgKeyFromPackageInfo } from '../../../../services';
|
import { pkgKeyFromPackageInfo } from '../../../../services';
|
||||||
import type {
|
import type {
|
||||||
CreatePackagePolicyRouteState,
|
CreatePackagePolicyRouteState,
|
||||||
|
@ -102,11 +98,13 @@ export function Detail() {
|
||||||
const { getId: getAgentPolicyId } = useAgentPolicyContext();
|
const { getId: getAgentPolicyId } = useAgentPolicyContext();
|
||||||
const { pkgkey, panel } = useParams<DetailParams>();
|
const { pkgkey, panel } = useParams<DetailParams>();
|
||||||
const { getHref } = useLink();
|
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 permissionCheck = usePermissionCheck();
|
||||||
const missingSecurityConfiguration =
|
const missingSecurityConfiguration =
|
||||||
!permissionCheck.data?.success && permissionCheck.data?.error === 'MISSING_SECURITY';
|
!permissionCheck.data?.success && permissionCheck.data?.error === 'MISSING_SECURITY';
|
||||||
const userCanInstallIntegrations = hasWriteCapabilities && permissionCheck.data?.success;
|
const userCanInstallPackages = canInstallPackages && permissionCheck.data?.success;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { pathname, search, hash } = useLocation();
|
const { pathname, search, hash } = useLocation();
|
||||||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||||
|
@ -367,7 +365,7 @@ export function Detail() {
|
||||||
content: (
|
content: (
|
||||||
<EuiButtonWithTooltip
|
<EuiButtonWithTooltip
|
||||||
fill
|
fill
|
||||||
isDisabled={!userCanInstallIntegrations}
|
isDisabled={!userCanInstallPackages}
|
||||||
iconType="plusInCircle"
|
iconType="plusInCircle"
|
||||||
href={getHref('add_integration_to_policy', {
|
href={getHref('add_integration_to_policy', {
|
||||||
pkgkey,
|
pkgkey,
|
||||||
|
@ -379,17 +377,17 @@ export function Detail() {
|
||||||
onClick={handleAddIntegrationPolicyClick}
|
onClick={handleAddIntegrationPolicyClick}
|
||||||
data-test-subj="addIntegrationPolicyButton"
|
data-test-subj="addIntegrationPolicyButton"
|
||||||
tooltip={
|
tooltip={
|
||||||
!userCanInstallIntegrations
|
!userCanInstallPackages
|
||||||
? {
|
? {
|
||||||
content: missingSecurityConfiguration ? (
|
content: missingSecurityConfiguration ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.epm.addPackagePolicyButtonSecurityRequiredTooltip"
|
id="xpack.fleet.epm.addPackagePolicyButtonSecurityRequiredTooltip"
|
||||||
defaultMessage="To add Elastic Agent Integrations, you must have security enabled and have the superuser role. Contact your administrator."
|
defaultMessage="To add Elastic Agent Integrations, you must have security enabled and have the All privilege for Fleet. Contact your administrator."
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.epm.addPackagePolicyButtonPrivilegesRequiredTooltip"
|
id="xpack.fleet.epm.addPackagePolicyButtonPrivilegesRequiredTooltip"
|
||||||
defaultMessage="To add Elastic Agent integrations, you must have the superuser role. Contact your adminstrator."
|
defaultMessage="Elastic Agent Integrations require the All privilege for Fleet and All privilege for Integrations. Contact your administrator."
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -427,7 +425,7 @@ export function Detail() {
|
||||||
packageInfo,
|
packageInfo,
|
||||||
updateAvailable,
|
updateAvailable,
|
||||||
packageInstallStatus,
|
packageInstallStatus,
|
||||||
userCanInstallIntegrations,
|
userCanInstallPackages,
|
||||||
getHref,
|
getHref,
|
||||||
pkgkey,
|
pkgkey,
|
||||||
integration,
|
integration,
|
||||||
|
@ -462,7 +460,7 @@ export function Detail() {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (userCanInstallIntegrations && packageInstallStatus === InstallStatus.installed) {
|
if (canReadIntegrationPolicies && packageInstallStatus === InstallStatus.installed) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'policies',
|
id: 'policies',
|
||||||
name: (
|
name: (
|
||||||
|
@ -498,7 +496,7 @@ export function Detail() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userCanInstallIntegrations) {
|
if (canReadPackageSettings) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
name: (
|
name: (
|
||||||
|
@ -540,7 +538,8 @@ export function Detail() {
|
||||||
panel,
|
panel,
|
||||||
getHref,
|
getHref,
|
||||||
integration,
|
integration,
|
||||||
userCanInstallIntegrations,
|
canReadIntegrationPolicies,
|
||||||
|
canReadPackageSettings,
|
||||||
packageInstallStatus,
|
packageInstallStatus,
|
||||||
CustomAssets,
|
CustomAssets,
|
||||||
showCustomTab,
|
showCustomTab,
|
||||||
|
@ -628,7 +627,7 @@ export function Detail() {
|
||||||
|
|
||||||
type EuiButtonPropsFull = Parameters<typeof EuiButton>[0];
|
type EuiButtonPropsFull = Parameters<typeof EuiButton>[0];
|
||||||
|
|
||||||
const EuiButtonWithTooltip: React.FC<
|
export const EuiButtonWithTooltip: React.FC<
|
||||||
EuiButtonPropsFull & { tooltip?: Partial<EuiToolTipProps> }
|
EuiButtonPropsFull & { tooltip?: Partial<EuiToolTipProps> }
|
||||||
> = ({ tooltip: tooltipProps, ...buttonProps }) => {
|
> = ({ tooltip: tooltipProps, ...buttonProps }) => {
|
||||||
return tooltipProps ? (
|
return tooltipProps ? (
|
||||||
|
|
|
@ -33,6 +33,7 @@ import {
|
||||||
AgentPolicyRefreshContext,
|
AgentPolicyRefreshContext,
|
||||||
useUIExtension,
|
useUIExtension,
|
||||||
usePackageInstallations,
|
usePackageInstallations,
|
||||||
|
useAuthz,
|
||||||
} from '../../../../../hooks';
|
} from '../../../../../hooks';
|
||||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||||
import {
|
import {
|
||||||
|
@ -104,6 +105,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
const { updatableIntegrations } = usePackageInstallations();
|
const { updatableIntegrations } = usePackageInstallations();
|
||||||
const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout');
|
const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout');
|
||||||
|
|
||||||
|
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||||
|
|
||||||
const packageAndAgentPolicies = useMemo((): Array<{
|
const packageAndAgentPolicies = useMemo((): Array<{
|
||||||
agentPolicy: GetAgentPoliciesResponseItem;
|
agentPolicy: GetAgentPoliciesResponseItem;
|
||||||
packagePolicy: InMemoryPackagePolicy;
|
packagePolicy: InMemoryPackagePolicy;
|
||||||
|
@ -244,6 +247,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
policyId: agentPolicy.id,
|
policyId: agentPolicy.id,
|
||||||
packagePolicyId: packagePolicy.id,
|
packagePolicyId: packagePolicy.id,
|
||||||
})}?from=integrations-policy-list`}
|
})}?from=integrations-policy-list`}
|
||||||
|
isDisabled={!canWriteIntegrationPolicies}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton"
|
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton"
|
||||||
|
@ -329,7 +333,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[getHref, showAddAgentHelpForPackagePolicyId, viewDataStep]
|
[getHref, showAddAgentHelpForPackagePolicyId, viewDataStep, canWriteIntegrationPolicies]
|
||||||
);
|
);
|
||||||
|
|
||||||
const noItemsMessage = useMemo(() => {
|
const noItemsMessage = useMemo(() => {
|
||||||
|
|
|
@ -11,11 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
|
||||||
import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../../../types';
|
import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../../../types';
|
||||||
import { InstallStatus } from '../../../../../types';
|
import { InstallStatus } from '../../../../../types';
|
||||||
import {
|
import { useAuthz, useGetPackageInstallStatus, useInstallPackage } from '../../../../../hooks';
|
||||||
useCapabilities,
|
|
||||||
useGetPackageInstallStatus,
|
|
||||||
useInstallPackage,
|
|
||||||
} from '../../../../../hooks';
|
|
||||||
|
|
||||||
import { ConfirmPackageInstall } from './confirm_package_install';
|
import { ConfirmPackageInstall } from './confirm_package_install';
|
||||||
|
|
||||||
|
@ -30,7 +26,7 @@ type InstallationButtonProps = Pick<PackageInfo, 'name' | 'title' | 'version'> &
|
||||||
};
|
};
|
||||||
export function InstallButton(props: InstallationButtonProps) {
|
export function InstallButton(props: InstallationButtonProps) {
|
||||||
const { name, numOfAssets, title, version } = props;
|
const { name, numOfAssets, title, version } = props;
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const canInstallPackages = useAuthz().integrations.installPackages;
|
||||||
const installPackage = useInstallPackage();
|
const installPackage = useInstallPackage();
|
||||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||||
const { status: installationStatus } = getPackageInstallStatus(name);
|
const { status: installationStatus } = getPackageInstallStatus(name);
|
||||||
|
@ -56,7 +52,7 @@ export function InstallButton(props: InstallationButtonProps) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return hasWriteCapabilites ? (
|
return canInstallPackages ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<EuiButton iconType={'importAction'} isLoading={isInstalling} onClick={toggleInstallModal}>
|
<EuiButton iconType={'importAction'} isLoading={isInstalling} onClick={toggleInstallModal}>
|
||||||
{isInstalling ? (
|
{isInstalling ? (
|
||||||
|
|
|
@ -12,11 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { InstallStatus } from '../../../../../types';
|
import { InstallStatus } from '../../../../../types';
|
||||||
import type { PackageInfo } from '../../../../../types';
|
import type { PackageInfo } from '../../../../../types';
|
||||||
|
|
||||||
import {
|
import { useAuthz, useGetPackageInstallStatus, useUninstallPackage } from '../../../../../hooks';
|
||||||
useCapabilities,
|
|
||||||
useGetPackageInstallStatus,
|
|
||||||
useUninstallPackage,
|
|
||||||
} from '../../../../../hooks';
|
|
||||||
|
|
||||||
import { ConfirmPackageUninstall } from './confirm_package_uninstall';
|
import { ConfirmPackageUninstall } from './confirm_package_uninstall';
|
||||||
|
|
||||||
|
@ -34,7 +30,7 @@ export const UninstallButton: React.FunctionComponent<UninstallButtonProps> = ({
|
||||||
title,
|
title,
|
||||||
version,
|
version,
|
||||||
}) => {
|
}) => {
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const canRemovePackages = useAuthz().integrations.removePackages;
|
||||||
const uninstallPackage = useUninstallPackage();
|
const uninstallPackage = useUninstallPackage();
|
||||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||||
const { status: installationStatus } = getPackageInstallStatus(name);
|
const { status: installationStatus } = getPackageInstallStatus(name);
|
||||||
|
@ -59,7 +55,7 @@ export const UninstallButton: React.FunctionComponent<UninstallButtonProps> = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return hasWriteCapabilites ? (
|
return canRemovePackages ? (
|
||||||
<>
|
<>
|
||||||
<EuiButton
|
<EuiButton
|
||||||
iconType={'trash'}
|
iconType={'trash'}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
useGetPackageInstallStatus,
|
useGetPackageInstallStatus,
|
||||||
sendUpgradePackagePolicy,
|
sendUpgradePackagePolicy,
|
||||||
useStartServices,
|
useStartServices,
|
||||||
useCapabilities,
|
useAuthz,
|
||||||
useLink,
|
useLink,
|
||||||
} from '../../../../../hooks';
|
} from '../../../../../hooks';
|
||||||
import { toMountPoint } from '../../../../../../../../../../../src/plugins/kibana_react/public';
|
import { toMountPoint } from '../../../../../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
@ -82,7 +82,7 @@ export const UpdateButton: React.FunctionComponent<UpdateButtonProps> = ({
|
||||||
const { getPath } = useLink();
|
const { getPath } = useLink();
|
||||||
|
|
||||||
const { notifications } = useStartServices();
|
const { notifications } = useStartServices();
|
||||||
const hasWriteCapabilites = useCapabilities().write;
|
const canUpgradePackages = useAuthz().integrations.upgradePackages;
|
||||||
|
|
||||||
const installPackage = useInstallPackage();
|
const installPackage = useInstallPackage();
|
||||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||||
|
@ -287,7 +287,7 @@ export const UpdateButton: React.FunctionComponent<UpdateButtonProps> = ({
|
||||||
</EuiConfirmModal>
|
</EuiConfirmModal>
|
||||||
);
|
);
|
||||||
|
|
||||||
return hasWriteCapabilites ? (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiFlexGroup alignItems="center">
|
<EuiFlexGroup alignItems="center">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
|
@ -297,6 +297,7 @@ export const UpdateButton: React.FunctionComponent<UpdateButtonProps> = ({
|
||||||
upgradePackagePolicies ? () => setIsUpdateModalVisible(true) : handleClickUpdate
|
upgradePackagePolicies ? () => setIsUpdateModalVisible(true) : handleClickUpdate
|
||||||
}
|
}
|
||||||
data-test-subj="updatePackageBtn"
|
data-test-subj="updatePackageBtn"
|
||||||
|
isDisabled={!canUpgradePackages}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.fleet.integrations.updatePackage.updatePackageButtonLabel"
|
id="xpack.fleet.integrations.updatePackage.updatePackageButtonLabel"
|
||||||
|
@ -314,6 +315,7 @@ export const UpdateButton: React.FunctionComponent<UpdateButtonProps> = ({
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
id="upgradePoliciesCheckbox"
|
id="upgradePoliciesCheckbox"
|
||||||
|
disabled={!canUpgradePackages}
|
||||||
checked={upgradePackagePolicies}
|
checked={upgradePackagePolicies}
|
||||||
onChange={handleUpgradePackagePoliciesChange}
|
onChange={handleUpgradePackagePoliciesChange}
|
||||||
label={i18n.translate(
|
label={i18n.translate(
|
||||||
|
@ -329,5 +331,5 @@ export const UpdateButton: React.FunctionComponent<UpdateButtonProps> = ({
|
||||||
|
|
||||||
{isUpdateModalVisible && updateModal}
|
{isUpdateModalVisible && updateModal}
|
||||||
</>
|
</>
|
||||||
) : null;
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,17 +11,17 @@ import { EuiButtonEmpty } from '@elastic/eui';
|
||||||
import type { TutorialDirectoryHeaderLinkComponent } from 'src/plugins/home/public';
|
import type { TutorialDirectoryHeaderLinkComponent } from 'src/plugins/home/public';
|
||||||
|
|
||||||
import { RedirectAppLinks } from '../../../../../../src/plugins/kibana_react/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 TutorialDirectoryHeaderLink: TutorialDirectoryHeaderLinkComponent = memo(() => {
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const { application } = useStartServices();
|
const { application } = useStartServices();
|
||||||
const { show: hasIngestManager } = useCapabilities();
|
const hasIntegrationsPermissions = application.capabilities.navLinks.integrations;
|
||||||
const [noticeState] = useState({
|
const [noticeState] = useState({
|
||||||
settingsDataLoaded: false,
|
settingsDataLoaded: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return hasIngestManager && noticeState.settingsDataLoaded ? (
|
return hasIntegrationsPermissions && noticeState.settingsDataLoaded ? (
|
||||||
<RedirectAppLinks application={application}>
|
<RedirectAppLinks application={application}>
|
||||||
<EuiButtonEmpty size="s" iconType="link" flush="right" href={getHref('integrations')}>
|
<EuiButtonEmpty size="s" iconType="link" flush="right" href={getHref('integrations')}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
|
@ -11,13 +11,14 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { EuiText, EuiLink, EuiSpacer, EuiIcon } from '@elastic/eui';
|
import { EuiText, EuiLink, EuiSpacer, EuiIcon } from '@elastic/eui';
|
||||||
import type { TutorialModuleNoticeComponent } from 'src/plugins/home/public';
|
import type { TutorialModuleNoticeComponent } from 'src/plugins/home/public';
|
||||||
|
|
||||||
import { useGetPackages, useLink, useCapabilities } from '../../hooks';
|
import { useGetPackages, useLink, useStartServices } from '../../hooks';
|
||||||
import { pkgKeyFromPackageInfo } from '../../services';
|
import { pkgKeyFromPackageInfo } from '../../services';
|
||||||
import { FLEET_APM_PACKAGE } from '../../../common/constants';
|
import { FLEET_APM_PACKAGE } from '../../../common/constants';
|
||||||
|
|
||||||
const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }) => {
|
const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }) => {
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const { show: hasIngestManager } = useCapabilities();
|
const { application } = useStartServices();
|
||||||
|
const hasIntegrationsPermissions = application.capabilities.navLinks.integrations;
|
||||||
const { data: packagesData, isLoading } = useGetPackages();
|
const { data: packagesData, isLoading } = useGetPackages();
|
||||||
|
|
||||||
const pkgInfo =
|
const pkgInfo =
|
||||||
|
@ -25,7 +26,7 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }
|
||||||
packagesData?.response &&
|
packagesData?.response &&
|
||||||
packagesData.response.find((pkg) => pkg.name === moduleName && pkg.name !== FLEET_APM_PACKAGE); // APM needs special handling
|
packagesData.response.find((pkg) => pkg.name === moduleName && pkg.name !== FLEET_APM_PACKAGE); // APM needs special handling
|
||||||
|
|
||||||
if (hasIngestManager && pkgInfo) {
|
if (hasIntegrationsPermissions && pkgInfo) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiSpacer />
|
<EuiSpacer />
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
|
||||||
import type { AgentPolicy, InMemoryPackagePolicy } from '../types';
|
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 { AgentEnrollmentFlyout } from './agent_enrollment_flyout';
|
||||||
import { ContextMenuActions } from './context_menu_actions';
|
import { ContextMenuActions } from './context_menu_actions';
|
||||||
|
@ -36,7 +36,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||||
}) => {
|
}) => {
|
||||||
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
|
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
|
||||||
const { getHref } = useLink();
|
const { getHref } = useLink();
|
||||||
const hasWriteCapabilities = useCapabilities().write;
|
const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies;
|
||||||
const refreshAgentPolicy = useAgentPolicyRefresh();
|
const refreshAgentPolicy = useAgentPolicyRefresh();
|
||||||
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(defaultIsOpen);
|
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(defaultIsOpen);
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
disabled={!hasWriteCapabilities}
|
disabled={!canWriteIntegrationPolicies}
|
||||||
icon="pencil"
|
icon="pencil"
|
||||||
href={getHref('integration_policy_edit', {
|
href={getHref('integration_policy_edit', {
|
||||||
packagePolicyId: packagePolicy.id,
|
packagePolicyId: packagePolicy.id,
|
||||||
|
@ -88,7 +88,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||||
/>
|
/>
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
disabled={!packagePolicy.hasUpgrade}
|
disabled={!packagePolicy.hasUpgrade || !canWriteIntegrationPolicies}
|
||||||
icon="refresh"
|
icon="refresh"
|
||||||
href={upgradePackagePolicyHref}
|
href={upgradePackagePolicyHref}
|
||||||
key="packagePolicyUpgrade"
|
key="packagePolicyUpgrade"
|
||||||
|
@ -113,7 +113,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||||
{(deletePackagePoliciesPrompt) => {
|
{(deletePackagePoliciesPrompt) => {
|
||||||
return (
|
return (
|
||||||
<DangerEuiContextMenuItem
|
<DangerEuiContextMenuItem
|
||||||
disabled={!hasWriteCapabilities}
|
disabled={!canWriteIntegrationPolicies}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deletePackagePoliciesPrompt([packagePolicy.id], () => {
|
deletePackagePoliciesPrompt([packagePolicy.id], () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { useCapabilities } from './use_capabilities';
|
export { useAuthz } from './use_authz';
|
||||||
export { useStartServices } from './use_core';
|
export { useStartServices } from './use_core';
|
||||||
export { useConfig, ConfigContext } from './use_config';
|
export { useConfig, ConfigContext } from './use_config';
|
||||||
export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version';
|
export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version';
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import { useStartServices } from './use_core';
|
import { useStartServices } from './use_core';
|
||||||
|
|
||||||
export function useCapabilities() {
|
// Expose authz object, containing the privileges for Fleet and Integrations
|
||||||
|
export function useAuthz() {
|
||||||
const core = useStartServices();
|
const core = useStartServices();
|
||||||
return core.application.capabilities.fleet;
|
return core.authz;
|
||||||
}
|
}
|
|
@ -10,10 +10,11 @@ import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../
|
||||||
|
|
||||||
import { sendRequest, useRequest } from './use_request';
|
import { sendRequest, useRequest } from './use_request';
|
||||||
|
|
||||||
export const sendGetPermissionsCheck = () => {
|
export const sendGetPermissionsCheck = (fleetServerSetup?: boolean) => {
|
||||||
return sendRequest<CheckPermissionsResponse>({
|
return sendRequest<CheckPermissionsResponse>({
|
||||||
path: appRoutesService.getCheckPermissionsPath(),
|
path: appRoutesService.getCheckPermissionsPath(),
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
query: { fleetServerSetup },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,18 @@ const configureStartServices = (services: MockedFleetStartServices): void => {
|
||||||
// Store the http for use by useRequest
|
// Store the http for use by useRequest
|
||||||
setHttpClient(services.http);
|
setHttpClient(services.http);
|
||||||
|
|
||||||
// Set Fleet available capabilities
|
// Set Fleet and Integrations capabilities
|
||||||
services.application.capabilities = {
|
services.application.capabilities = {
|
||||||
...services.application.capabilities,
|
...services.application.capabilities,
|
||||||
|
// Fleet
|
||||||
|
fleetv2: {
|
||||||
|
read: true,
|
||||||
|
all: true,
|
||||||
|
},
|
||||||
|
// Integration
|
||||||
fleet: {
|
fleet: {
|
||||||
read: true,
|
read: true,
|
||||||
write: true,
|
all: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo
|
||||||
return {
|
return {
|
||||||
isInitialized: jest.fn().mockResolvedValue(true),
|
isInitialized: jest.fn().mockResolvedValue(true),
|
||||||
registerExtension: createExtensionRegistrationCallback(extensionsStorage),
|
registerExtension: createExtensionRegistrationCallback(extensionsStorage),
|
||||||
authz: Promise.resolve({
|
authz: {
|
||||||
fleet: {
|
fleet: {
|
||||||
all: true,
|
all: true,
|
||||||
setup: true,
|
setup: true,
|
||||||
|
@ -33,6 +33,6 @@ export const createStartMock = (extensionsStorage: UIExtensionsStorage = {}): Mo
|
||||||
readIntegrationPolicies: true,
|
readIntegrationPolicies: true,
|
||||||
writeIntegrationPolicies: true,
|
writeIntegrationPolicies: true,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,7 +76,7 @@ export interface FleetSetup {}
|
||||||
*/
|
*/
|
||||||
export interface FleetStart {
|
export interface FleetStart {
|
||||||
/** Authorization for the current user */
|
/** Authorization for the current user */
|
||||||
authz: Promise<FleetAuthz>;
|
authz: FleetAuthz;
|
||||||
registerExtension: UIExtensionRegistrationCallback;
|
registerExtension: UIExtensionRegistrationCallback;
|
||||||
isInitialized: () => Promise<true>;
|
isInitialized: () => Promise<true>;
|
||||||
}
|
}
|
||||||
|
@ -261,34 +261,21 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
|
||||||
view: 'package-detail-assets',
|
view: 'package-detail-assets',
|
||||||
Component: LazyCustomLogsAssetsExtension,
|
Component: LazyCustomLogsAssetsExtension,
|
||||||
});
|
});
|
||||||
|
const { capabilities } = core.application;
|
||||||
|
|
||||||
|
// capabilities.fleetv2 returns fleet privileges and capabilities.fleet returns integrations privileges
|
||||||
return {
|
return {
|
||||||
// Temporarily rely on superuser check to calculate authz. Once Kibana RBAC is in place for Fleet this should
|
authz: calculateAuthz({
|
||||||
// switch to a sync calculation based on `core.application.capabilites` properties.
|
fleet: {
|
||||||
authz: getPermissions()
|
all: capabilities.fleetv2.all as boolean,
|
||||||
.catch((e) => {
|
setup: false,
|
||||||
// eslint-disable-next-line no-console
|
},
|
||||||
console.warn(`Could not load Fleet permissions due to error: ${e}`);
|
integrations: {
|
||||||
return { success: false };
|
all: capabilities.fleet.all as boolean,
|
||||||
})
|
read: capabilities.fleet.read as boolean,
|
||||||
.then((permissionsResponse) => {
|
},
|
||||||
if (permissionsResponse?.success) {
|
isSuperuser: false,
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
isInitialized: once(async () => {
|
isInitialized: once(async () => {
|
||||||
const permissionsResponse = await getPermissions();
|
const permissionsResponse = await getPermissions();
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
import type { Observable } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
import type {
|
import type {
|
||||||
CoreSetup,
|
CoreSetup,
|
||||||
CoreStart,
|
CoreStart,
|
||||||
|
@ -222,14 +224,16 @@ export class FleetPlugin
|
||||||
registerEncryptedSavedObjects(deps.encryptedSavedObjects);
|
registerEncryptedSavedObjects(deps.encryptedSavedObjects);
|
||||||
|
|
||||||
// Register feature
|
// Register feature
|
||||||
// TODO: Flesh out privileges
|
|
||||||
if (deps.features) {
|
if (deps.features) {
|
||||||
deps.features.registerKibanaFeature({
|
deps.features.registerKibanaFeature({
|
||||||
id: PLUGIN_ID,
|
id: `fleetv2`,
|
||||||
name: 'Fleet and Integrations',
|
name: 'Fleet',
|
||||||
category: DEFAULT_APP_CATEGORIES.management,
|
category: DEFAULT_APP_CATEGORIES.management,
|
||||||
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
|
app: [PLUGIN_ID],
|
||||||
catalogue: ['fleet'],
|
catalogue: ['fleet'],
|
||||||
|
privilegesTooltip: i18n.translate('xpack.fleet.serverPlugin.privilegesTooltip', {
|
||||||
|
defaultMessage: 'All Spaces is required for Fleet access.',
|
||||||
|
}),
|
||||||
reserved: {
|
reserved: {
|
||||||
description:
|
description:
|
||||||
'Privilege to setup Fleet packages and configured policies. Intended for use by the elastic/fleet-server service account only.',
|
'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: {
|
privileges: {
|
||||||
all: {
|
all: {
|
||||||
api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`, `integrations-all`, `integrations-read`],
|
api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`],
|
||||||
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
|
app: [PLUGIN_ID],
|
||||||
|
requireAllSpaces: true,
|
||||||
catalogue: ['fleet'],
|
catalogue: ['fleet'],
|
||||||
savedObject: {
|
savedObject: {
|
||||||
all: allSavedObjectTypes,
|
all: allSavedObjectTypes,
|
||||||
read: [],
|
read: [],
|
||||||
},
|
},
|
||||||
ui: ['show', 'read', 'write'],
|
ui: ['read', 'all'],
|
||||||
},
|
},
|
||||||
read: {
|
read: {
|
||||||
api: [`${PLUGIN_ID}-read`, `integrations-read`],
|
api: [`${PLUGIN_ID}-read`],
|
||||||
app: [PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, 'kibana'],
|
app: [PLUGIN_ID],
|
||||||
catalogue: ['fleet'], // TODO: check if this is actually available to read user
|
catalogue: ['fleet'],
|
||||||
|
requireAllSpaces: true,
|
||||||
savedObject: {
|
savedObject: {
|
||||||
all: [],
|
all: [],
|
||||||
read: allSavedObjectTypes,
|
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'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -105,7 +105,7 @@ export const createAgentPolicyHandler: FleetRequestHandler<
|
||||||
TypeOf<typeof CreateAgentPolicyRequestSchema.query>,
|
TypeOf<typeof CreateAgentPolicyRequestSchema.query>,
|
||||||
TypeOf<typeof CreateAgentPolicyRequestSchema.body>
|
TypeOf<typeof CreateAgentPolicyRequestSchema.body>
|
||||||
> = async (context, request, response) => {
|
> = async (context, request, response) => {
|
||||||
const soClient = context.core.savedObjects.client;
|
const soClient = context.fleet.epm.internalSoClient;
|
||||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||||
const withSysMonitoring = request.query.sys_monitoring ?? false;
|
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 GetFullAgentPolicyRequestSchema.params>,
|
TypeOf<typeof GetFullAgentPolicyRequestSchema.params>,
|
||||||
TypeOf<typeof GetFullAgentPolicyRequestSchema.query>
|
TypeOf<typeof GetFullAgentPolicyRequestSchema.query>
|
||||||
> = async (context, request, response) => {
|
> = async (context, request, response) => {
|
||||||
const soClient = context.core.savedObjects.client;
|
const soClient = context.fleet.epm.internalSoClient;
|
||||||
|
|
||||||
if (request.query.kubernetes === true) {
|
if (request.query.kubernetes === true) {
|
||||||
try {
|
try {
|
||||||
|
@ -284,11 +284,11 @@ export const getFullAgentPolicy: RequestHandler<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadFullAgentPolicy: RequestHandler<
|
export const downloadFullAgentPolicy: FleetRequestHandler<
|
||||||
TypeOf<typeof GetFullAgentPolicyRequestSchema.params>,
|
TypeOf<typeof GetFullAgentPolicyRequestSchema.params>,
|
||||||
TypeOf<typeof GetFullAgentPolicyRequestSchema.query>
|
TypeOf<typeof GetFullAgentPolicyRequestSchema.query>
|
||||||
> = async (context, request, response) => {
|
> = async (context, request, response) => {
|
||||||
const soClient = context.core.savedObjects.client;
|
const soClient = context.fleet.epm.internalSoClient;
|
||||||
const {
|
const {
|
||||||
params: { agentPolicyId },
|
params: { agentPolicyId },
|
||||||
} = request;
|
} = request;
|
||||||
|
|
|
@ -6,14 +6,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { RequestHandler } from 'src/core/server';
|
import type { RequestHandler } from 'src/core/server';
|
||||||
|
import type { TypeOf } from '@kbn/config-schema';
|
||||||
|
|
||||||
import { APP_API_ROUTES } from '../../constants';
|
import { APP_API_ROUTES } from '../../constants';
|
||||||
import { appContextService } from '../../services';
|
import { appContextService } from '../../services';
|
||||||
import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../../common';
|
import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../../common';
|
||||||
import { defaultIngestErrorHandler, GenerateServiceTokenError } from '../../errors';
|
import { defaultIngestErrorHandler, GenerateServiceTokenError } from '../../errors';
|
||||||
import type { FleetAuthzRouter } from '../security';
|
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<typeof CheckPermissionsRequestSchema.query>
|
||||||
|
> = async (context, request, response) => {
|
||||||
const missingSecurityBody: CheckPermissionsResponse = {
|
const missingSecurityBody: CheckPermissionsResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'MISSING_SECURITY',
|
error: 'MISSING_SECURITY',
|
||||||
|
@ -22,25 +28,32 @@ export const getCheckPermissionsHandler: RequestHandler = async (context, reques
|
||||||
if (!appContextService.getSecurityLicense().isEnabled()) {
|
if (!appContextService.getSecurityLicense().isEnabled()) {
|
||||||
return response.ok({ body: missingSecurityBody });
|
return response.ok({ body: missingSecurityBody });
|
||||||
} else {
|
} else {
|
||||||
const security = appContextService.getSecurity();
|
if (!context.fleet.authz.fleet.all) {
|
||||||
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')) {
|
|
||||||
return response.ok({
|
return response.ok({
|
||||||
body: {
|
body: {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'MISSING_SUPERUSER_ROLE',
|
error: 'MISSING_PRIVILEGES',
|
||||||
} as CheckPermissionsResponse,
|
} 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 });
|
return response.ok({ body: { success: true } as CheckPermissionsResponse });
|
||||||
}
|
}
|
||||||
|
@ -77,9 +90,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
||||||
router.get(
|
router.get(
|
||||||
{
|
{
|
||||||
path: APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
|
path: APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
|
||||||
validate: {},
|
validate: CheckPermissionsRequestSchema,
|
||||||
options: { tags: [] },
|
options: { tags: [] },
|
||||||
// no permission check for that route
|
|
||||||
},
|
},
|
||||||
getCheckPermissionsHandler
|
getCheckPermissionsHandler
|
||||||
);
|
);
|
||||||
|
|
|
@ -86,7 +86,7 @@ export const createPackagePolicyHandler: FleetRequestHandler<
|
||||||
undefined,
|
undefined,
|
||||||
TypeOf<typeof CreatePackagePolicyRequestSchema.body>
|
TypeOf<typeof CreatePackagePolicyRequestSchema.body>
|
||||||
> = async (context, request, response) => {
|
> = async (context, request, response) => {
|
||||||
const soClient = context.core.savedObjects.client;
|
const soClient = context.fleet.epm.internalSoClient;
|
||||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||||
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
|
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
|
||||||
const { force, ...newPolicy } = request.body;
|
const { force, ...newPolicy } = request.body;
|
||||||
|
|
|
@ -106,7 +106,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
|
||||||
path: PACKAGE_POLICY_API_ROUTES.DRYRUN_PATTERN,
|
path: PACKAGE_POLICY_API_ROUTES.DRYRUN_PATTERN,
|
||||||
validate: DryRunPackagePoliciesRequestSchema,
|
validate: DryRunPackagePoliciesRequestSchema,
|
||||||
fleetAuthz: {
|
fleetAuthz: {
|
||||||
integrations: { writeIntegrationPolicies: true },
|
integrations: { readIntegrationPolicies: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dryRunUpgradePackagePolicyHandler
|
dryRunUpgradePackagePolicyHandler
|
||||||
|
|
|
@ -19,7 +19,10 @@ import { makeRouterWithFleetAuthz } from './security';
|
||||||
function getCheckPrivilegesMockedImplementation(kibanaRoles: string[]) {
|
function getCheckPrivilegesMockedImplementation(kibanaRoles: string[]) {
|
||||||
return (checkPrivileges: CheckPrivilegesPayload) => {
|
return (checkPrivileges: CheckPrivilegesPayload) => {
|
||||||
const kibana = ((checkPrivileges?.kibana ?? []) as string[]).map((role: string) => {
|
const kibana = ((checkPrivileges?.kibana ?? []) as string[]).map((role: string) => {
|
||||||
return { authorized: kibanaRoles.includes(role) };
|
return {
|
||||||
|
privilege: role,
|
||||||
|
authorized: kibanaRoles.includes(role),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
@ -141,14 +144,6 @@ describe('FleetAuthzRouter', () => {
|
||||||
path: '/api/fleet/test',
|
path: '/api/fleet/test',
|
||||||
fleetAuthz: { fleet: { setup: true } },
|
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 () => {
|
it('allow users with fleet-setup role', async () => {
|
||||||
mockCheckPrivileges.mockImplementation(
|
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', () => {
|
describe('with fleet role', () => {
|
||||||
const routeConfig = {
|
const routeConfig = {
|
||||||
path: '/api/fleet/test',
|
path: '/api/fleet/test',
|
||||||
fleetAuthz: { integrations: { readPackageInfo: true } },
|
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 () => {
|
it('allow users with all required fleet authz role', async () => {
|
||||||
mockCheckPrivileges.mockImplementation(
|
mockCheckPrivileges.mockImplementation(
|
||||||
getCheckPrivilegesMockedImplementation(['api:integrations-read'])
|
getCheckPrivilegesMockedImplementation(['api:integrations-read'])
|
||||||
|
|
|
@ -16,10 +16,11 @@ import type {
|
||||||
} from 'src/core/server';
|
} from 'src/core/server';
|
||||||
|
|
||||||
import type { FleetAuthz } from '../../common';
|
import type { FleetAuthz } from '../../common';
|
||||||
import { calculateAuthz } from '../../common';
|
import { calculateAuthz, INTEGRATIONS_PLUGIN_ID } from '../../common';
|
||||||
|
|
||||||
import { appContextService } from '../services';
|
import { appContextService } from '../services';
|
||||||
import type { FleetRequestHandlerContext } from '../types';
|
import type { FleetRequestHandlerContext } from '../types';
|
||||||
|
import { PLUGIN_ID } from '../constants';
|
||||||
|
|
||||||
function checkSecurityEnabled() {
|
function checkSecurityEnabled() {
|
||||||
return appContextService.getSecurityLicense().isEnabled();
|
return appContextService.getSecurityLicense().isEnabled();
|
||||||
|
@ -44,60 +45,47 @@ export function checkSuperuser(req: KibanaRequest) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkFleetSetupPrivilege(req: KibanaRequest) {
|
function getAuthorizationFromPrivileges(
|
||||||
if (!checkSecurityEnabled()) {
|
kibanaPrivileges: Array<{
|
||||||
return false;
|
resource?: string;
|
||||||
}
|
privilege: string;
|
||||||
|
authorized: boolean;
|
||||||
const security = appContextService.getSecurity();
|
}>,
|
||||||
|
searchPrivilege: string
|
||||||
if (security.authz.mode.useRbacForRequest(req)) {
|
) {
|
||||||
const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req);
|
const privilege = kibanaPrivileges.find((p) => p.privilege.includes(searchPrivilege));
|
||||||
const { hasAllRequested } = await checkPrivileges(
|
return privilege ? privilege.authorized : false;
|
||||||
{ kibana: [security.authz.actions.api.get('fleet-setup')] },
|
|
||||||
{ requireLoginAction: false } // exclude login access requirement
|
|
||||||
);
|
|
||||||
return !!hasAllRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAuthzFromRequest(req: KibanaRequest): Promise<FleetAuthz> {
|
export async function getAuthzFromRequest(req: KibanaRequest): Promise<FleetAuthz> {
|
||||||
const security = appContextService.getSecurity();
|
const security = appContextService.getSecurity();
|
||||||
|
|
||||||
if (security.authz.mode.useRbacForRequest(req)) {
|
if (security.authz.mode.useRbacForRequest(req)) {
|
||||||
if (checkSuperuser(req)) {
|
const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req);
|
||||||
// Superusers get access to everything
|
const { privileges } = await checkPrivileges({
|
||||||
// Once we implement Kibana RBAC, remove this and use `checkPrivileges` exclusively
|
kibana: [
|
||||||
return calculateAuthz({
|
security.authz.actions.api.get(`${PLUGIN_ID}-all`),
|
||||||
fleet: { all: true, setup: true },
|
security.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-all`),
|
||||||
integrations: { all: true, read: true },
|
security.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-read`),
|
||||||
isSuperuser: true,
|
security.authz.actions.api.get('fleet-setup'),
|
||||||
});
|
],
|
||||||
} else if (await checkFleetSetupPrivilege(req)) {
|
});
|
||||||
// fleet-setup privilege only gets access to setup actions
|
const fleetAllAuth = getAuthorizationFromPrivileges(privileges.kibana, `${PLUGIN_ID}-all`);
|
||||||
return calculateAuthz({
|
const intAllAuth = getAuthorizationFromPrivileges(
|
||||||
fleet: { all: false, setup: true },
|
privileges.kibana,
|
||||||
integrations: { all: false, read: false },
|
`${INTEGRATIONS_PLUGIN_ID}-all`
|
||||||
isSuperuser: false,
|
);
|
||||||
});
|
const intReadAuth = getAuthorizationFromPrivileges(
|
||||||
} else {
|
privileges.kibana,
|
||||||
// All other users only get access to read integrations if they have the read privilege
|
`${INTEGRATIONS_PLUGIN_ID}-read`
|
||||||
const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req);
|
);
|
||||||
const { privileges } = await checkPrivileges({
|
const fleetSetupAuth = getAuthorizationFromPrivileges(privileges.kibana, 'fleet-setup');
|
||||||
kibana: [security.authz.actions.api.get('integrations-read')],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [intRead] = privileges.kibana;
|
return calculateAuthz({
|
||||||
|
fleet: { all: fleetAllAuth, setup: fleetSetupAuth },
|
||||||
// Once we implement Kibana RBAC, use `checkPrivileges` for all privileges instead of only integrations.read
|
integrations: { all: intAllAuth, read: intReadAuth },
|
||||||
return calculateAuthz({
|
isSuperuser: checkSuperuser(req),
|
||||||
fleet: { all: false, setup: false },
|
});
|
||||||
integrations: { all: false, read: intRead.authorized },
|
|
||||||
isSuperuser: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculateAuthz({
|
return calculateAuthz({
|
||||||
|
|
|
@ -5,17 +5,17 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { RequestHandler } from 'src/core/server';
|
|
||||||
import type { TypeOf } from '@kbn/config-schema';
|
import type { TypeOf } from '@kbn/config-schema';
|
||||||
|
|
||||||
import { SETTINGS_API_ROUTES } from '../../constants';
|
import { SETTINGS_API_ROUTES } from '../../constants';
|
||||||
|
import type { FleetRequestHandler } from '../../types';
|
||||||
import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types';
|
import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types';
|
||||||
import { defaultIngestErrorHandler } from '../../errors';
|
import { defaultIngestErrorHandler } from '../../errors';
|
||||||
import { settingsService, agentPolicyService, appContextService } from '../../services';
|
import { settingsService, agentPolicyService, appContextService } from '../../services';
|
||||||
import type { FleetAuthzRouter } from '../security';
|
import type { FleetAuthzRouter } from '../security';
|
||||||
|
|
||||||
export const getSettingsHandler: RequestHandler = async (context, request, response) => {
|
export const getSettingsHandler: FleetRequestHandler = async (context, request, response) => {
|
||||||
const soClient = context.core.savedObjects.client;
|
const soClient = context.fleet.epm.internalSoClient;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await settingsService.getSettings(soClient);
|
const settings = await settingsService.getSettings(soClient);
|
||||||
|
@ -26,7 +26,7 @@ export const getSettingsHandler: RequestHandler = async (context, request, respo
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.isBoom && error.output.statusCode === 404) {
|
if (error.isBoom && error.output.statusCode === 404) {
|
||||||
return response.notFound({
|
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,
|
||||||
undefined,
|
undefined,
|
||||||
TypeOf<typeof PutSettingsRequestSchema.body>
|
TypeOf<typeof PutSettingsRequestSchema.body>
|
||||||
> = async (context, request, response) => {
|
> = async (context, request, response) => {
|
||||||
const soClient = context.core.savedObjects.client;
|
const soClient = context.fleet.epm.internalSoClient;
|
||||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||||
const user = await appContextService.getSecurity()?.authc.getCurrentUser(request);
|
const user = await appContextService.getSecurity()?.authc.getCurrentUser(request);
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ export const putSettingsHandler: RequestHandler<
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.isBoom && error.output.statusCode === 404) {
|
if (error.isBoom && error.output.statusCode === 404) {
|
||||||
return response.notFound({
|
return response.notFound({
|
||||||
body: { message: `Setings not found` },
|
body: { message: `Settings not found` },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,15 @@ import type { ElasticsearchClient } from '../../../../../../src/core/server';
|
||||||
import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
|
import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
|
||||||
import { FleetUnauthorizedError } from '../../errors';
|
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 type { AgentClient } from './agent_service';
|
||||||
import { AgentServiceImpl } from './agent_service';
|
import { AgentServiceImpl } from './agent_service';
|
||||||
import { getAgentsByKuery, getAgentById } from './crud';
|
import { getAgentsByKuery, getAgentById } from './crud';
|
||||||
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
|
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
|
||||||
|
|
||||||
const mockCheckSuperuser = checkSuperuser as jest.Mock<boolean>;
|
const mockGetAuthzFromRequest = getAuthzFromRequest as jest.Mock<Promise<FleetAuthz>>;
|
||||||
const mockGetAgentsByKuery = getAgentsByKuery as jest.Mock;
|
const mockGetAgentsByKuery = getAgentsByKuery as jest.Mock;
|
||||||
const mockGetAgentById = getAgentById as jest.Mock;
|
const mockGetAgentById = getAgentById as jest.Mock;
|
||||||
const mockGetAgentStatusById = getAgentStatusById as jest.Mock;
|
const mockGetAgentStatusById = getAgentStatusById as jest.Mock;
|
||||||
|
@ -37,7 +38,30 @@ describe('AgentService', () => {
|
||||||
elasticsearchServiceMock.createElasticsearchClient()
|
elasticsearchServiceMock.createElasticsearchClient()
|
||||||
).asScoped(httpServerMock.createKibanaRequest());
|
).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 () => {
|
it('rejects on listAgents', async () => {
|
||||||
await expect(agentClient.listAgents({ showInactive: true })).rejects.toThrowError(
|
await expect(agentClient.listAgents({ showInactive: true })).rejects.toThrowError(
|
||||||
|
@ -78,7 +102,30 @@ describe('AgentService', () => {
|
||||||
httpServerMock.createKibanaRequest()
|
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);
|
expectApisToCallServicesSuccessfully(mockEsClient, agentClient);
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type { ElasticsearchClient, KibanaRequest } from 'kibana/server';
|
||||||
import type { AgentStatus, ListWithKuery } from '../../types';
|
import type { AgentStatus, ListWithKuery } from '../../types';
|
||||||
import type { Agent, GetAgentStatusResponse } from '../../../common';
|
import type { Agent, GetAgentStatusResponse } from '../../../common';
|
||||||
|
|
||||||
import { checkSuperuser } from '../../routes/security';
|
import { getAuthzFromRequest } from '../../routes/security';
|
||||||
|
|
||||||
import { FleetUnauthorizedError } from '../../errors';
|
import { FleetUnauthorizedError } from '../../errors';
|
||||||
|
|
||||||
|
@ -123,8 +123,9 @@ export class AgentServiceImpl implements AgentService {
|
||||||
constructor(private readonly internalEsClient: ElasticsearchClient) {}
|
constructor(private readonly internalEsClient: ElasticsearchClient) {}
|
||||||
|
|
||||||
public asScoped(req: KibanaRequest) {
|
public asScoped(req: KibanaRequest) {
|
||||||
const preflightCheck = () => {
|
const preflightCheck = async () => {
|
||||||
if (!checkSuperuser(req)) {
|
const authz = await getAuthzFromRequest(req);
|
||||||
|
if (!authz.fleet.all) {
|
||||||
throw new FleetUnauthorizedError(
|
throw new FleetUnauthorizedError(
|
||||||
`User does not have adequate permissions to access Fleet agents.`
|
`User does not have adequate permissions to access Fleet agents.`
|
||||||
);
|
);
|
||||||
|
|
|
@ -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()),
|
||||||
|
}),
|
||||||
|
};
|
|
@ -15,3 +15,4 @@ export * from './output';
|
||||||
export * from './preconfiguration';
|
export * from './preconfiguration';
|
||||||
export * from './settings';
|
export * from './settings';
|
||||||
export * from './setup';
|
export * from './setup';
|
||||||
|
export * from './check_permissions';
|
||||||
|
|
|
@ -34,7 +34,6 @@ describe('When using useEndpointPrivileges hook', () => {
|
||||||
let authenticatedUser: AuthenticatedUser;
|
let authenticatedUser: AuthenticatedUser;
|
||||||
let result: RenderResult<EndpointPrivileges>;
|
let result: RenderResult<EndpointPrivileges>;
|
||||||
let unmount: ReturnType<typeof renderHook>['unmount'];
|
let unmount: ReturnType<typeof renderHook>['unmount'];
|
||||||
let releaseFleetAuthz: () => void;
|
|
||||||
let render: () => RenderHookResult<void, EndpointPrivileges>;
|
let render: () => RenderHookResult<void, EndpointPrivileges>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -46,14 +45,6 @@ describe('When using useEndpointPrivileges hook', () => {
|
||||||
|
|
||||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(true);
|
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 = () => {
|
render = () => {
|
||||||
const hookRenderResponse = renderHook(() => useEndpointPrivileges());
|
const hookRenderResponse = renderHook(() => useEndpointPrivileges());
|
||||||
({ result, unmount } = hookRenderResponse);
|
({ result, unmount } = hookRenderResponse);
|
||||||
|
@ -78,7 +69,6 @@ describe('When using useEndpointPrivileges hook', () => {
|
||||||
|
|
||||||
// Release the API response
|
// Release the API response
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
releaseFleetAuthz();
|
|
||||||
await useKibana().services.fleet!.authz;
|
await useKibana().services.fleet!.authz;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11653,7 +11653,6 @@
|
||||||
"xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage": "名前が必要です",
|
"xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage": "名前が必要です",
|
||||||
"xpack.fleet.packagePolicyValidation.quoteStringErrorMessage": "*や&などの特殊YAML文字で始まる文字列は二重引用符で囲む必要があります。",
|
"xpack.fleet.packagePolicyValidation.quoteStringErrorMessage": "*や&などの特殊YAML文字で始まる文字列は二重引用符で囲む必要があります。",
|
||||||
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "{fieldName}が必要です",
|
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "{fieldName}が必要です",
|
||||||
"xpack.fleet.permissionDeniedErrorMessage": "Fleet へのアクセスが許可されていません。Fleet には{roleName}権限が必要です。",
|
|
||||||
"xpack.fleet.permissionDeniedErrorTitle": "パーミッションが拒否されました",
|
"xpack.fleet.permissionDeniedErrorTitle": "パーミッションが拒否されました",
|
||||||
"xpack.fleet.permissionsRequestErrorMessageDescription": "Fleet アクセス権の確認中に問題が発生しました",
|
"xpack.fleet.permissionsRequestErrorMessageDescription": "Fleet アクセス権の確認中に問題が発生しました",
|
||||||
"xpack.fleet.permissionsRequestErrorMessageTitle": "アクセス権を確認できません",
|
"xpack.fleet.permissionsRequestErrorMessageTitle": "アクセス権を確認できません",
|
||||||
|
|
|
@ -11777,7 +11777,6 @@
|
||||||
"xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage": "“名称”必填",
|
"xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage": "“名称”必填",
|
||||||
"xpack.fleet.packagePolicyValidation.quoteStringErrorMessage": "以特殊 YAML 字符(* 或 &)开头的字符串需要使用双引号引起。",
|
"xpack.fleet.packagePolicyValidation.quoteStringErrorMessage": "以特殊 YAML 字符(* 或 &)开头的字符串需要使用双引号引起。",
|
||||||
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "“{fieldName}”必填",
|
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "“{fieldName}”必填",
|
||||||
"xpack.fleet.permissionDeniedErrorMessage": "您无权访问 Fleet。Fleet 需要 {roleName} 权限。",
|
|
||||||
"xpack.fleet.permissionDeniedErrorTitle": "权限被拒绝",
|
"xpack.fleet.permissionDeniedErrorTitle": "权限被拒绝",
|
||||||
"xpack.fleet.permissionsRequestErrorMessageDescription": "检查 Fleet 权限时遇到问题",
|
"xpack.fleet.permissionsRequestErrorMessageDescription": "检查 Fleet 权限时遇到问题",
|
||||||
"xpack.fleet.permissionsRequestErrorMessageTitle": "无法检查权限",
|
"xpack.fleet.permissionsRequestErrorMessageTitle": "无法检查权限",
|
||||||
|
|
|
@ -119,6 +119,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
'siem',
|
'siem',
|
||||||
'securitySolutionCases',
|
'securitySolutionCases',
|
||||||
'fleet',
|
'fleet',
|
||||||
|
'fleetv2',
|
||||||
].sort()
|
].sort()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
canvas: ['all', 'read', 'minimal_all', 'minimal_read'],
|
canvas: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
maps: ['all', 'read', 'minimal_all', 'minimal_read'],
|
maps: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
observabilityCases: ['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'],
|
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
|
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
|
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
|
|
|
@ -40,6 +40,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
ml: ['all', 'read', 'minimal_all', 'minimal_read'],
|
ml: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
siem: ['all', 'read', 'minimal_all', 'minimal_read'],
|
siem: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
securitySolutionCases: ['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'],
|
fleet: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
|
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
|
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue