mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Fleet] moved root privilege callout with data stream info to create/edit package policy page (#184190)
## Summary Address feedback in https://github.com/elastic/kibana/pull/184119#issuecomment-2127689576 Relates https://github.com/elastic/ingest-dev/issues/3357 Moved root privileges callout with data streams from package policy submit modal to the create/edit package policy page itself, so it is more persistent than a modal window. To verify: - Go to System integration / Add integration - Verify that the require root callout shows the data streams that require root <img width="974" alt="image" src="bafdd556
-c837-414d-8bbc-26a4463a8390"> - Go to System integration / Existing policies / Edit integration - Verify that the require root callout is visible with data stream info <img width="901" alt="image" src="793ace68
-7618-482e-a200-6b831d293c99"> - For package where all data streams require root, the callout is unchanged. <img width="876" alt="image" src="902f7d3c
-ddbc-4131-a19d-341aa1209430"> <img width="878" alt="image" src="085e32df
-033d-41ca-9805-5414854d9750"> - The require root callout is removed from the submit confirmation modal. <img width="1135" alt="image" src="e360d74b
-09d1-4a41-b2ff-f4a36656e3d4"> ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5a74376da0
commit
8ecee1f7e1
8 changed files with 75 additions and 241 deletions
|
@ -11,7 +11,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { AgentPolicy } from '../../../types';
|
||||
import { UnprivilegedAgentsCallout } from '../create_package_policy_page/single_page_layout/confirm_modal';
|
||||
|
||||
export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{
|
||||
onConfirm: () => void;
|
||||
|
@ -76,16 +75,6 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{
|
|||
/>
|
||||
</div>
|
||||
</EuiCallOut>
|
||||
{showUnprivilegedAgentsCallout && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<UnprivilegedAgentsCallout
|
||||
agentPolicyName={agentPolicy.name}
|
||||
unprivilegedAgentsCount={unprivilegedAgentsCount}
|
||||
dataStreams={dataStreams ?? []}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicy.confirmModalDescription"
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* 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 { EuiCallOut, EuiConfirmModal } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export interface UnprivilegedConfirmModalProps {
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
agentPolicyName: string;
|
||||
unprivilegedAgentsCount: number;
|
||||
dataStreams: Array<{ name: string; title: string }>;
|
||||
}
|
||||
|
||||
export const UnprivilegedConfirmModal: React.FC<UnprivilegedConfirmModalProps> = ({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
agentPolicyName,
|
||||
unprivilegedAgentsCount,
|
||||
dataStreams,
|
||||
}: UnprivilegedConfirmModalProps) => {
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.addIntegration.confirmModalTitle"
|
||||
defaultMessage="Confirm add integration"
|
||||
/>
|
||||
}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
cancelButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.addIntegration.confirmModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
}
|
||||
confirmButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.addIntegration.confirmModal.confirmButtonLabel"
|
||||
defaultMessage="Add integration"
|
||||
/>
|
||||
}
|
||||
buttonColor="warning"
|
||||
>
|
||||
<UnprivilegedAgentsCallout
|
||||
unprivilegedAgentsCount={unprivilegedAgentsCount}
|
||||
agentPolicyName={agentPolicyName}
|
||||
dataStreams={dataStreams}
|
||||
/>
|
||||
</EuiConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
export const UnprivilegedAgentsCallout: React.FC<{
|
||||
agentPolicyName: string;
|
||||
unprivilegedAgentsCount: number;
|
||||
dataStreams: Array<{ name: string; title: string }>;
|
||||
}> = ({ agentPolicyName, unprivilegedAgentsCount, dataStreams }) => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
title={i18n.translate('xpack.fleet.addIntegration.confirmModal.unprivilegedAgentsTitle', {
|
||||
defaultMessage: 'Unprivileged agents enrolled to the selected policy',
|
||||
})}
|
||||
data-test-subj="unprivilegedAgentsCallout"
|
||||
>
|
||||
{dataStreams.length === 0 ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.addIntegration.confirmModal.unprivilegedAgentsMessage"
|
||||
defaultMessage="This integration requires Elastic Agents to have root privileges. There {unprivilegedAgentsCount, plural, one {is # agent} other {are # agents}} running in an unprivileged mode using {agentPolicyName}. To ensure that all data required by the integration can be collected, re-enroll the {unprivilegedAgentsCount, plural, one {agent} other {agents}} using an account with root privileges."
|
||||
values={{
|
||||
unprivilegedAgentsCount,
|
||||
agentPolicyName,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.addIntegration.confirmModal.unprivilegedAgentsDataStreamsMessage"
|
||||
defaultMessage="This integration has the following data streams that require Elastic Agents to have root privileges. There {unprivilegedAgentsCount, plural, one {is # agent} other {are # agents}} running in an unprivileged mode using {agentPolicyName}. To ensure that all data required by the integration can be collected, re-enroll the {unprivilegedAgentsCount, plural, one {agent} other {agents}} using an account with root privileges."
|
||||
values={{
|
||||
unprivilegedAgentsCount,
|
||||
agentPolicyName,
|
||||
}}
|
||||
/>
|
||||
<ul>
|
||||
{dataStreams.map((item) => (
|
||||
<li key={item.name}>{item.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -31,10 +31,7 @@ import {
|
|||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
SO_SEARCH_LIMIT,
|
||||
} from '../../../../../../../../common';
|
||||
import {
|
||||
getMaxPackageName,
|
||||
isRootPrivilegesRequired,
|
||||
} from '../../../../../../../../common/services';
|
||||
import { getMaxPackageName } from '../../../../../../../../common/services';
|
||||
import { useConfirmForceInstall } from '../../../../../../integrations/hooks';
|
||||
import { validatePackagePolicy, validationHasErrors } from '../../services';
|
||||
import type { PackagePolicyValidationResults } from '../../services';
|
||||
|
@ -270,16 +267,6 @@ export function useOnSubmit({
|
|||
setFormState('CONFIRM');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
packageInfo &&
|
||||
isRootPrivilegesRequired(packageInfo) &&
|
||||
(agentPolicy?.unprivileged_agents ?? 0) > 0 &&
|
||||
formState !== 'CONFIRM' &&
|
||||
formState !== 'CONFIRM_UNPRIVILEGED'
|
||||
) {
|
||||
setFormState('CONFIRM_UNPRIVILEGED');
|
||||
return;
|
||||
}
|
||||
let createdPolicy = overrideCreatedAgentPolicy;
|
||||
if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) {
|
||||
try {
|
||||
|
|
|
@ -316,7 +316,7 @@ describe('When on the package policy create page', () => {
|
|||
(sendCreatePackagePolicy as jest.MockedFunction<any>).mockClear();
|
||||
});
|
||||
|
||||
test('should show unprivileged warning modal on submit if conditions match', async () => {
|
||||
test('should show root privileges callout on create page', async () => {
|
||||
(useGetPackageInfoByKeyQuery as jest.Mock).mockReturnValue(
|
||||
getMockPackageInfo({ requiresRoot: true })
|
||||
);
|
||||
|
@ -324,70 +324,15 @@ describe('When on the package policy create page', () => {
|
|||
render('agent-policy-1');
|
||||
});
|
||||
|
||||
let saveBtn: HTMLElement;
|
||||
|
||||
await waitFor(() => {
|
||||
saveBtn = renderResult.getByText(/Save and continue/).closest('button')!;
|
||||
expect(saveBtn).not.toBeDisabled();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(saveBtn);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
renderResult.getByText('Unprivileged agents enrolled to the selected policy')
|
||||
).toBeInTheDocument();
|
||||
expect(renderResult.getByTestId('unprivilegedAgentsCallout').textContent).toContain(
|
||||
'This integration requires Elastic Agents to have root privileges. There is 1 agent running in an unprivileged mode using Agent policy 1. To ensure that all data required by the integration can be collected, re-enroll the agent using an account with root privileges.'
|
||||
expect(renderResult.getByText('Requires root privileges')).toBeInTheDocument();
|
||||
expect(renderResult.getByTestId('rootPrivilegesCallout').textContent).toContain(
|
||||
'Elastic Agent needs to be run with root/administrator privileges for this integration.'
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
saveBtn = renderResult.getByText(/Add integration/).closest('button')!;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(saveBtn);
|
||||
});
|
||||
|
||||
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should show unprivileged warning and agents modal on submit if conditions match', async () => {
|
||||
(useGetPackageInfoByKeyQuery as jest.Mock).mockReturnValue(
|
||||
getMockPackageInfo({ requiresRoot: true })
|
||||
);
|
||||
(sendGetAgentStatus as jest.MockedFunction<any>).mockResolvedValueOnce({
|
||||
data: { results: { total: 1 } },
|
||||
});
|
||||
await act(async () => {
|
||||
render('agent-policy-1');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByText('This action will update 1 agent')).toBeInTheDocument();
|
||||
expect(
|
||||
renderResult.getByText('Unprivileged agents enrolled to the selected policy')
|
||||
).toBeInTheDocument();
|
||||
expect(renderResult.getByTestId('unprivilegedAgentsCallout').textContent).toContain(
|
||||
'This integration requires Elastic Agents to have root privileges. There is 1 agent running in an unprivileged mode using Agent policy 1. To ensure that all data required by the integration can be collected, re-enroll the agent using an account with root privileges.'
|
||||
);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(renderResult.getAllByText(/Save and deploy changes/)[1].closest('button')!);
|
||||
});
|
||||
|
||||
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should show unprivileged warning modal with data streams on submit if conditions match', async () => {
|
||||
test('should show root privileges callout with data streams on create page', async () => {
|
||||
(useGetPackageInfoByKeyQuery as jest.Mock).mockReturnValue(
|
||||
getMockPackageInfo({ dataStreamRequiresRoot: true })
|
||||
);
|
||||
|
@ -395,38 +340,15 @@ describe('When on the package policy create page', () => {
|
|||
render('agent-policy-1');
|
||||
});
|
||||
|
||||
let saveBtn: HTMLElement;
|
||||
|
||||
await waitFor(() => {
|
||||
saveBtn = renderResult.getByText(/Save and continue/).closest('button')!;
|
||||
expect(saveBtn).not.toBeDisabled();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(saveBtn);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
renderResult.getByText('Unprivileged agents enrolled to the selected policy')
|
||||
).toBeInTheDocument();
|
||||
expect(renderResult.getByTestId('unprivilegedAgentsCallout').textContent).toContain(
|
||||
'This integration has the following data streams that require Elastic Agents to have root privileges. There is 1 agent running in an unprivileged mode using Agent policy 1. To ensure that all data required by the integration can be collected, re-enroll the agent using an account with root privileges.'
|
||||
expect(renderResult.getByText('Requires root privileges')).toBeInTheDocument();
|
||||
expect(renderResult.getByTestId('rootPrivilegesCallout').textContent).toContain(
|
||||
'This integration has the following data streams that require Elastic Agents to have root privileges. To ensure that all data required by the integration can be collected, enroll agents using an account with root privileges.'
|
||||
);
|
||||
expect(renderResult.getByTestId('unprivilegedAgentsCallout').textContent).toContain(
|
||||
expect(renderResult.getByTestId('rootPrivilegesCallout').textContent).toContain(
|
||||
'Nginx access logs'
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
saveBtn = renderResult.getByText(/Add integration/).closest('button')!;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(saveBtn);
|
||||
});
|
||||
|
||||
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should create package policy on submit when query param agent policy id is set', async () => {
|
||||
|
|
|
@ -79,7 +79,7 @@ import { useDevToolsRequest, useOnSubmit, useSetupTechnology } from './hooks';
|
|||
import { PostInstallCloudFormationModal } from './components/cloud_security_posture/post_install_cloud_formation_modal';
|
||||
import { PostInstallGoogleCloudShellModal } from './components/cloud_security_posture/post_install_google_cloud_shell_modal';
|
||||
import { PostInstallAzureArmTemplateModal } from './components/cloud_security_posture/post_install_azure_arm_template_modal';
|
||||
import { UnprivilegedConfirmModal } from './confirm_modal';
|
||||
import { RootPrivilegesCallout } from './root_callout';
|
||||
|
||||
const StepsWithLessPadding = styled(EuiSteps)`
|
||||
.euiStep__content {
|
||||
|
@ -463,15 +463,6 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
|
|||
dataStreams={rootPrivilegedDataStreams}
|
||||
/>
|
||||
)}
|
||||
{formState === 'CONFIRM_UNPRIVILEGED' && agentPolicy ? (
|
||||
<UnprivilegedConfirmModal
|
||||
onCancel={() => setFormState('VALID')}
|
||||
onConfirm={onSubmit}
|
||||
unprivilegedAgentsCount={agentPolicy?.unprivileged_agents ?? 0}
|
||||
agentPolicyName={agentPolicy?.name ?? ''}
|
||||
dataStreams={rootPrivilegedDataStreams}
|
||||
/>
|
||||
) : null}
|
||||
{formState === 'SUBMITTED_NO_AGENTS' &&
|
||||
agentPolicy &&
|
||||
packageInfo &&
|
||||
|
@ -515,21 +506,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
|
|||
)}
|
||||
{packageInfo && isRootPrivilegesRequired(packageInfo) ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.requireRootCalloutTitle"
|
||||
defaultMessage="Requires root privileges"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.requireRootCalloutDescription"
|
||||
defaultMessage="Elastic Agent needs to be run with root/administrator privileges for this integration."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<RootPrivilegesCallout dataStreams={rootPrivilegedDataStreams} />
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { EuiCallOut } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
dataStreams: Array<{ name: string; title: string }>;
|
||||
}
|
||||
|
||||
export const RootPrivilegesCallout: React.FC<Props> = ({ dataStreams }) => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
size="m"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.requireRootCalloutTitle"
|
||||
defaultMessage="Requires root privileges"
|
||||
/>
|
||||
}
|
||||
data-test-subj="rootPrivilegesCallout"
|
||||
>
|
||||
{dataStreams.length === 0 ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.requireRootCalloutDescription"
|
||||
defaultMessage="Elastic Agent needs to be run with root/administrator privileges for this integration."
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.addIntegration.confirmModal.unprivilegedAgentsDataStreamsMessage"
|
||||
defaultMessage="This integration has the following data streams that require Elastic Agents to have root privileges. To ensure that all data required by the integration can be collected, enroll agents using an account with root privileges."
|
||||
/>
|
||||
<ul>
|
||||
{dataStreams.map((item) => (
|
||||
<li key={item.name}>{item.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -19,7 +19,6 @@ export type PackagePolicyFormState =
|
|||
| 'VALID'
|
||||
| 'INVALID'
|
||||
| 'CONFIRM'
|
||||
| 'CONFIRM_UNPRIVILEGED'
|
||||
| 'LOADING'
|
||||
| 'SUBMITTED'
|
||||
| 'SUBMITTED_NO_AGENTS'
|
||||
|
|
|
@ -55,6 +55,13 @@ import type { PackagePolicyEditExtensionComponentProps } from '../../../types';
|
|||
import { ExperimentalFeaturesService, pkgKeyFromPackageInfo } from '../../../services';
|
||||
import { generateUpdatePackagePolicyDevToolsRequest } from '../services';
|
||||
|
||||
import {
|
||||
getRootPrivilegedDataStreams,
|
||||
isRootPrivilegesRequired,
|
||||
} from '../../../../../../common/services';
|
||||
|
||||
import { RootPrivilegesCallout } from '../create_package_policy_page/single_page_layout/root_callout';
|
||||
|
||||
import { UpgradeStatusCallout } from './components';
|
||||
import { usePackagePolicyWithRelatedData, useHistoryBlock } from './hooks';
|
||||
import { getNewSecrets } from './utils';
|
||||
|
@ -387,6 +394,7 @@ export const EditPackagePolicyForm = memo<{
|
|||
),
|
||||
[packagePolicyId, packagePolicy]
|
||||
);
|
||||
const rootPrivilegedDataStreams = packageInfo ? getRootPrivilegedDataStreams(packageInfo) : [];
|
||||
|
||||
return (
|
||||
<CreatePackagePolicySinglePageLayout {...layoutProps} data-test-subj="editPackagePolicy">
|
||||
|
@ -426,6 +434,12 @@ export const EditPackagePolicyForm = memo<{
|
|||
onCancel={() => setFormState('VALID')}
|
||||
/>
|
||||
)}
|
||||
{packageInfo && isRootPrivilegesRequired(packageInfo) ? (
|
||||
<>
|
||||
<RootPrivilegesCallout dataStreams={rootPrivilegedDataStreams} />
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
{isUpgrade && upgradeDryRunData && (
|
||||
<>
|
||||
<UpgradeStatusCallout dryRunData={upgradeDryRunData} newSecrets={newSecrets} />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue