mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Endpoint] Remove use of Redux from the Endpoint Policy Settings form (#161511)
## Summary - Re-creates all policy settings form components so that the Policy settings are provided as a prop - Adds `mode = view` to the policy form. When in this mode (user has no authz to edit), form will be displayed in view only mode (no more `disabled` form elements)
This commit is contained in:
parent
f022456ad8
commit
3ba51e4a31
65 changed files with 2801 additions and 2842 deletions
|
@ -15,9 +15,10 @@ import type { MaybeImmutable, NewPolicyData, PolicyData } from '../../types';
|
|||
*/
|
||||
export const getPolicyDataForUpdate = (policy: MaybeImmutable<PolicyData>): NewPolicyData => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { id, revision, created_by, created_at, updated_by, updated_at, ...newPolicy } = policy;
|
||||
// cast to `NewPolicyData` (mutable) since we cloned the entire object
|
||||
const policyDataForUpdate = cloneDeep(newPolicy) as NewPolicyData;
|
||||
const { id, revision, created_by, created_at, updated_by, updated_at, ...rest } =
|
||||
policy as PolicyData;
|
||||
|
||||
const policyDataForUpdate: NewPolicyData = cloneDeep(rest);
|
||||
const endpointPolicy = policyDataForUpdate.inputs[0].config.policy.value;
|
||||
|
||||
// trim custom malware notification string
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getPolicySettingsFormTestSubjects } from '../../../pages/policy/view/policy_settings_form/mocks';
|
||||
import { ProtectionModes } from '../../../../../common/endpoint/types';
|
||||
import {
|
||||
PackagePolicyBackupHelper,
|
||||
|
@ -30,7 +31,7 @@ describe('Policy Details', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
visitPolicyDetailsPage();
|
||||
visitPolicyDetailsPage(indexedHostsData.data.integrationPolicies[0].id);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -42,33 +43,43 @@ describe('Policy Details', () => {
|
|||
});
|
||||
|
||||
describe('Malware Protection card', () => {
|
||||
const malwareTestSubj = getPolicySettingsFormTestSubjects().malware;
|
||||
|
||||
it('user should be able to see related rules', () => {
|
||||
cy.getByTestSubj('malwareProtectionsForm').contains('related detection rules').click();
|
||||
cy.getByTestSubj(malwareTestSubj.card).contains('related detection rules').click();
|
||||
|
||||
cy.url().should('contain', 'app/security/rules/management');
|
||||
});
|
||||
|
||||
it('changing protection level should enable or disable user notification', () => {
|
||||
cy.getByTestSubj('malwareProtectionSwitch').click();
|
||||
cy.getByTestSubj('malwareProtectionSwitch').should('have.attr', 'aria-checked', 'true');
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click();
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should(
|
||||
'have.attr',
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
|
||||
// Default: Prevent + Notify user enabled
|
||||
cy.getByTestSubj('malwareProtectionMode_prevent').find('input').should('be.checked');
|
||||
cy.getByTestSubj('malwareUserNotificationCheckbox').should('be.checked');
|
||||
cy.getByTestSubj(malwareTestSubj.protectionPreventRadio).find('input').should('be.checked');
|
||||
cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('be.checked');
|
||||
|
||||
// Changing to Detect -> Notify user disabled
|
||||
cy.getByTestSubj('malwareProtectionMode_detect').find('label').click();
|
||||
cy.getByTestSubj('malwareUserNotificationCheckbox').should('not.be.checked');
|
||||
cy.getByTestSubj(malwareTestSubj.protectionDetectRadio).find('label').click();
|
||||
cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('not.be.checked');
|
||||
|
||||
// Changing back to Prevent -> Notify user enabled
|
||||
cy.getByTestSubj('malwareProtectionMode_prevent').find('label').click();
|
||||
cy.getByTestSubj('malwareUserNotificationCheckbox').should('be.checked');
|
||||
cy.getByTestSubj(malwareTestSubj.protectionPreventRadio).find('label').click();
|
||||
cy.getByTestSubj(malwareTestSubj.notifyUserCheckbox).should('be.checked');
|
||||
});
|
||||
|
||||
it('disabling protection should disable notification in yaml for every OS', () => {
|
||||
// Enable malware protection and user notification
|
||||
cy.getByTestSubj('malwareProtectionSwitch').click();
|
||||
cy.getByTestSubj('malwareProtectionSwitch').should('have.attr', 'aria-checked', 'true');
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click();
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should(
|
||||
'have.attr',
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
savePolicyForm();
|
||||
|
||||
yieldPolicyConfig().then((policyConfig) => {
|
||||
|
@ -78,8 +89,12 @@ describe('Policy Details', () => {
|
|||
});
|
||||
|
||||
// disable malware protection
|
||||
cy.getByTestSubj('malwareProtectionSwitch').click();
|
||||
cy.getByTestSubj('malwareProtectionSwitch').should('have.attr', 'aria-checked', 'false');
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click();
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).should(
|
||||
'have.attr',
|
||||
'aria-checked',
|
||||
'false'
|
||||
);
|
||||
savePolicyForm();
|
||||
|
||||
yieldPolicyConfig().then((policyConfig) => {
|
||||
|
@ -96,11 +111,12 @@ describe('Policy Details', () => {
|
|||
expect(policyConfig.windows.malware.mode).to.equal(ProtectionModes.off);
|
||||
});
|
||||
|
||||
cy.getByTestSubj('malwareProtectionsForm').should('contain.text', 'Linux');
|
||||
cy.getByTestSubj('malwareProtectionsForm').should('contain.text', 'Windows');
|
||||
cy.getByTestSubj('malwareProtectionsForm').should('contain.text', 'Mac');
|
||||
cy.getByTestSubj(malwareTestSubj.osValuesContainer).should(
|
||||
'contain.text',
|
||||
'Windows, Mac, Linux'
|
||||
);
|
||||
|
||||
cy.getByTestSubj('malwareProtectionSwitch').click();
|
||||
cy.getByTestSubj(malwareTestSubj.enableDisableSwitch).click();
|
||||
savePolicyForm();
|
||||
|
||||
yieldPolicyConfig().then((policyConfig) => {
|
||||
|
@ -112,40 +128,50 @@ describe('Policy Details', () => {
|
|||
});
|
||||
|
||||
describe('Ransomware Protection card', () => {
|
||||
const ransomwareTestSubj = getPolicySettingsFormTestSubjects().ransomware;
|
||||
|
||||
it('user should be able to see related rules', () => {
|
||||
cy.getByTestSubj('ransomwareProtectionsForm').contains('related detection rules').click();
|
||||
cy.getByTestSubj(ransomwareTestSubj.card).contains('related detection rules').click();
|
||||
|
||||
cy.url().should('contain', 'app/security/rules/management');
|
||||
});
|
||||
|
||||
it('changing protection level should enable or disable user notification', () => {
|
||||
cy.getByTestSubj('ransomwareProtectionSwitch').click();
|
||||
cy.getByTestSubj('ransomwareProtectionSwitch').should('have.attr', 'aria-checked', 'true');
|
||||
cy.getByTestSubj(ransomwareTestSubj.enableDisableSwitch).click();
|
||||
cy.getByTestSubj(ransomwareTestSubj.enableDisableSwitch).should(
|
||||
'have.attr',
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
|
||||
// Default: Prevent + Notify user enabled
|
||||
cy.getByTestSubj('ransomwareProtectionMode_prevent').find('input').should('be.checked');
|
||||
cy.getByTestSubj('ransomwareUserNotificationCheckbox').should('be.checked');
|
||||
cy.getByTestSubj(ransomwareTestSubj.protectionPreventRadio)
|
||||
.find('input')
|
||||
.should('be.checked');
|
||||
cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('be.checked');
|
||||
|
||||
// Changing to Detect -> Notify user disabled
|
||||
cy.getByTestSubj('ransomwareProtectionMode_detect').find('label').click();
|
||||
cy.getByTestSubj('ransomwareUserNotificationCheckbox').should('not.be.checked');
|
||||
cy.getByTestSubj(ransomwareTestSubj.protectionDetectRadio).find('label').click();
|
||||
cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('not.be.checked');
|
||||
|
||||
// Changing back to Prevent -> Notify user enabled
|
||||
cy.getByTestSubj('ransomwareProtectionMode_prevent').find('label').click();
|
||||
cy.getByTestSubj('ransomwareUserNotificationCheckbox').should('be.checked');
|
||||
cy.getByTestSubj(ransomwareTestSubj.protectionPreventRadio).find('label').click();
|
||||
cy.getByTestSubj(ransomwareTestSubj.notifyUserCheckbox).should('be.checked');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Advanced settings', () => {
|
||||
const testSubjects = getPolicySettingsFormTestSubjects().advancedSection;
|
||||
|
||||
it('should show empty text inputs except for some settings', () => {
|
||||
const settingsWithDefaultValues = [
|
||||
'mac.advanced.capture_env_vars',
|
||||
'linux.advanced.capture_env_vars',
|
||||
];
|
||||
|
||||
cy.getByTestSubj('advancedPolicyButton').click();
|
||||
cy.getByTestSubj(testSubjects.showHideButton).click();
|
||||
|
||||
cy.getByTestSubj('advancedPolicyPanel')
|
||||
cy.getByTestSubj(testSubjects.settingsContainer)
|
||||
.children()
|
||||
.each(($child) => {
|
||||
const settingName = $child.find('label').text();
|
||||
|
@ -167,8 +193,8 @@ describe('Policy Details', () => {
|
|||
});
|
||||
|
||||
// Set agent.connection_delay entry for every OS
|
||||
cy.getByTestSubj('advancedPolicyButton').click();
|
||||
cy.getByTestSubj('advancedPolicyPanel')
|
||||
cy.getByTestSubj(testSubjects.showHideButton).click();
|
||||
cy.getByTestSubj(testSubjects.settingsContainer)
|
||||
.children()
|
||||
.each(($child) => {
|
||||
const settingName = $child.find('label').text();
|
||||
|
|
|
@ -18,10 +18,13 @@ import type { PolicyConfig } from '../../../../common/endpoint/types';
|
|||
import { request, loadPage } from '../tasks/common';
|
||||
import { expectAndCloseSuccessToast } from '../tasks/toasts';
|
||||
|
||||
export const visitPolicyDetailsPage = () => {
|
||||
loadPage(APP_POLICIES_PATH);
|
||||
|
||||
cy.getByTestSubj('policyNameCellLink').eq(0).click({ force: true });
|
||||
export const visitPolicyDetailsPage = (policyId?: string) => {
|
||||
if (policyId) {
|
||||
loadPage(`${APP_POLICIES_PATH}/${policyId}`);
|
||||
} else {
|
||||
cy.visit(APP_POLICIES_PATH);
|
||||
cy.getByTestSubj('policyNameCellLink').eq(0).click({ force: true });
|
||||
}
|
||||
cy.getByTestSubj('policyDetailsPage').should('exist');
|
||||
cy.get('#settings').should('exist'); // waiting for Policy Settings tab
|
||||
};
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
||||
import type { IHttpFetchError } from '@kbn/core-http-browser';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { packagePolicyRouteService } from '@kbn/fleet-plugin/common';
|
||||
import {
|
||||
DefaultPolicyNotificationMessage,
|
||||
DefaultPolicyRuleNotificationMessage,
|
||||
} from '../../../../common/endpoint/models/policy_config';
|
||||
import type { GetPolicyResponse } from '../../pages/policy/types';
|
||||
import { useHttp } from '../../../common/lib/kibana';
|
||||
import type { PolicyData, PolicyConfig } from '../../../../common/endpoint/types';
|
||||
import type { ManifestSchema } from '../../../../common/endpoint/schema/manifest';
|
||||
|
||||
interface ApiDataResponse {
|
||||
/** Data return from the Fleet API. Its the full integration policy (package policy) */
|
||||
item: PolicyData;
|
||||
/** Endpoint policy settings from the data retrieved from fleet */
|
||||
settings: PolicyConfig;
|
||||
/** Endpoint policy manifest info from the data retrieved from fleet */
|
||||
artifactManifest: ManifestSchema;
|
||||
}
|
||||
|
||||
type UseFetchEndpointPolicyResponse = UseQueryResult<ApiDataResponse, IHttpFetchError>;
|
||||
|
||||
/**
|
||||
* Retrieve a single endpoint integration policy (details)
|
||||
* @param policyId
|
||||
* @param options
|
||||
*/
|
||||
export const useFetchEndpointPolicy = (
|
||||
policyId: string,
|
||||
options: UseQueryOptions<ApiDataResponse, IHttpFetchError> = {}
|
||||
): UseFetchEndpointPolicyResponse => {
|
||||
const http = useHttp();
|
||||
|
||||
return useQuery<ApiDataResponse, IHttpFetchError>({
|
||||
queryKey: ['get-policy-details', policyId],
|
||||
...options,
|
||||
queryFn: async () => {
|
||||
const apiResponse = await http.get<GetPolicyResponse>(
|
||||
packagePolicyRouteService.getInfoPath(policyId)
|
||||
);
|
||||
|
||||
applyDefaultsToPolicyIfNeeded(apiResponse.item);
|
||||
|
||||
return {
|
||||
item: apiResponse.item,
|
||||
settings: apiResponse.item.inputs[0].config.policy.value,
|
||||
artifactManifest: apiResponse.item.inputs[0].config.artifact_manifest.value,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const applyDefaultsToPolicyIfNeeded = (policyItem: PolicyData): void => {
|
||||
const settings = policyItem.inputs[0].config.policy.value;
|
||||
|
||||
// sets default user notification message if policy config message is empty
|
||||
if (settings.windows.popup.malware.message === '') {
|
||||
settings.windows.popup.malware.message = DefaultPolicyNotificationMessage;
|
||||
settings.mac.popup.malware.message = DefaultPolicyNotificationMessage;
|
||||
settings.linux.popup.malware.message = DefaultPolicyNotificationMessage;
|
||||
}
|
||||
if (settings.windows.popup.ransomware.message === '') {
|
||||
settings.windows.popup.ransomware.message = DefaultPolicyNotificationMessage;
|
||||
}
|
||||
if (settings.windows.popup.memory_protection.message === '') {
|
||||
settings.windows.popup.memory_protection.message = DefaultPolicyRuleNotificationMessage;
|
||||
}
|
||||
if (settings.mac.popup.memory_protection.message === '') {
|
||||
settings.mac.popup.memory_protection.message = DefaultPolicyRuleNotificationMessage;
|
||||
}
|
||||
if (settings.linux.popup.memory_protection.message === '') {
|
||||
settings.linux.popup.memory_protection.message = DefaultPolicyRuleNotificationMessage;
|
||||
}
|
||||
if (settings.windows.popup.behavior_protection.message === '') {
|
||||
settings.windows.popup.behavior_protection.message = DefaultPolicyRuleNotificationMessage;
|
||||
}
|
||||
if (settings.mac.popup.behavior_protection.message === '') {
|
||||
settings.mac.popup.behavior_protection.message = DefaultPolicyRuleNotificationMessage;
|
||||
}
|
||||
if (settings.linux.popup.behavior_protection.message === '') {
|
||||
settings.linux.popup.behavior_protection.message = DefaultPolicyRuleNotificationMessage;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
||||
import type { IHttpFetchError } from '@kbn/core-http-browser';
|
||||
import type { GetAgentStatusResponse } from '@kbn/fleet-plugin/common';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { agentRouteService } from '@kbn/fleet-plugin/common';
|
||||
import { useHttp } from '../../../common/lib/kibana';
|
||||
|
||||
type EndpointPolicyAgentSummary = GetAgentStatusResponse['results'];
|
||||
|
||||
export const useFetchAgentByAgentPolicySummary = (
|
||||
/**
|
||||
* The Fleet Agent Policy ID (NOT the endpoint policy id)
|
||||
*/
|
||||
agentPolicyId: string,
|
||||
options: UseQueryOptions<EndpointPolicyAgentSummary, IHttpFetchError> = {}
|
||||
): UseQueryResult<EndpointPolicyAgentSummary, IHttpFetchError> => {
|
||||
const http = useHttp();
|
||||
|
||||
return useQuery<EndpointPolicyAgentSummary, IHttpFetchError>({
|
||||
queryKey: ['get-policy-agent-summary', agentPolicyId],
|
||||
...options,
|
||||
queryFn: async () => {
|
||||
return (
|
||||
await http.get<GetAgentStatusResponse>(agentRouteService.getStatusPath(), {
|
||||
query: { policyId: agentPolicyId },
|
||||
})
|
||||
).results;
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
|
||||
import type { IHttpFetchError } from '@kbn/core-http-browser';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { packagePolicyRouteService } from '@kbn/fleet-plugin/common';
|
||||
import { getPolicyDataForUpdate } from '../../../../common/endpoint/service/policy';
|
||||
import { useHttp } from '../../../common/lib/kibana';
|
||||
import type { PolicyData } from '../../../../common/endpoint/types';
|
||||
import type { UpdatePolicyResponse } from '../../pages/policy/types';
|
||||
|
||||
interface UpdateParams {
|
||||
policy: PolicyData;
|
||||
}
|
||||
|
||||
type UseUpdateEndpointPolicyOptions = UseMutationOptions<
|
||||
UpdatePolicyResponse,
|
||||
IHttpFetchError,
|
||||
UpdateParams
|
||||
>;
|
||||
|
||||
type UseUpdateEndpointPolicyResult = UseMutationResult<
|
||||
UpdatePolicyResponse,
|
||||
IHttpFetchError,
|
||||
UpdateParams
|
||||
>;
|
||||
|
||||
export const useUpdateEndpointPolicy = (
|
||||
options?: UseUpdateEndpointPolicyOptions
|
||||
): UseUpdateEndpointPolicyResult => {
|
||||
const http = useHttp();
|
||||
|
||||
return useMutation<UpdatePolicyResponse, IHttpFetchError, UpdateParams>(({ policy }) => {
|
||||
const update = getPolicyDataForUpdate(policy);
|
||||
|
||||
return http.put(packagePolicyRouteService.getUpdatePath(policy.id), {
|
||||
body: JSON.stringify(update),
|
||||
});
|
||||
}, options);
|
||||
};
|
|
@ -1,23 +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 { cloneDeep } from 'lodash';
|
||||
import type { UIPolicyConfig } from '../../../../../common/endpoint/types';
|
||||
|
||||
/**
|
||||
* Returns cloned `configuration` with `value` set by the `keyPath`.
|
||||
*/
|
||||
export const setIn =
|
||||
(a: UIPolicyConfig) =>
|
||||
<Key extends keyof UIPolicyConfig>(key: Key) =>
|
||||
<SubKey extends keyof UIPolicyConfig[Key]>(subKey: SubKey) =>
|
||||
<LeafKey extends keyof UIPolicyConfig[Key][SubKey]>(leafKey: LeafKey) =>
|
||||
<V extends UIPolicyConfig[Key][SubKey][LeafKey]>(v: V): UIPolicyConfig => {
|
||||
const c = cloneDeep(a);
|
||||
c[key][subKey][leafKey] = v;
|
||||
return c;
|
||||
};
|
|
@ -186,14 +186,6 @@ export const policyConfig: (s: PolicyDetailsState) => UIPolicyConfig = createSel
|
|||
}
|
||||
);
|
||||
|
||||
export const isAntivirusRegistrationEnabled = createSelector(policyConfig, (uiPolicyConfig) => {
|
||||
return uiPolicyConfig.windows.antivirus_registration.enabled;
|
||||
});
|
||||
|
||||
export const isCredentialHardeningEnabled = createSelector(policyConfig, (uiPolicyConfig) => {
|
||||
return uiPolicyConfig.windows.attack_surface_reduction.credential_hardening.enabled;
|
||||
});
|
||||
|
||||
/** is there an api call in flight */
|
||||
export const isLoading = (state: PolicyDetailsState) => state.isLoading;
|
||||
|
||||
|
|
|
@ -1,82 +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, { memo, useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui';
|
||||
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { isAntivirusRegistrationEnabled } from '../../../store/policy_details/selectors';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { ConfigForm } from '../config_form';
|
||||
|
||||
const TRANSLATIONS: Readonly<{ [K in 'title' | 'description' | 'label']: string }> = {
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type',
|
||||
{
|
||||
defaultMessage: 'Register as antivirus',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation',
|
||||
{
|
||||
defaultMessage:
|
||||
'Toggle on to register Elastic as an official Antivirus solution for Windows OS. ' +
|
||||
'This will also disable Windows Defender.',
|
||||
}
|
||||
),
|
||||
label: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle',
|
||||
{
|
||||
defaultMessage: 'Register as antivirus',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const AntivirusRegistrationForm = memo(() => {
|
||||
const antivirusRegistrationEnabled = usePolicyDetailsSelector(isAntivirusRegistrationEnabled);
|
||||
const dispatch = useDispatch();
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) =>
|
||||
dispatch({
|
||||
type: 'userChangedAntivirusRegistration',
|
||||
payload: {
|
||||
enabled: event.target.checked,
|
||||
},
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfigForm
|
||||
type={TRANSLATIONS.title}
|
||||
supportedOss={[OperatingSystem.WINDOWS]}
|
||||
dataTestSubj="antivirusRegistrationForm"
|
||||
osRestriction={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.av.windowsServerNotSupported',
|
||||
{
|
||||
defaultMessage:
|
||||
'Windows Server operating systems unsupported because Antivirus registration requires Windows Security Center, which is not included in Windows Server operating systems.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiText size="s">{TRANSLATIONS.description}</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiSwitch
|
||||
label={TRANSLATIONS.label}
|
||||
checked={antivirusRegistrationEnabled}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={!showEditableFormFields}
|
||||
/>
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
||||
AntivirusRegistrationForm.displayName = 'AntivirusRegistrationForm';
|
|
@ -1,61 +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, { memo, useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSwitch } from '@elastic/eui';
|
||||
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { isCredentialHardeningEnabled } from '../../../store/policy_details/selectors';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { ConfigForm } from '../config_form';
|
||||
|
||||
const TRANSLATIONS: Readonly<{ [K in 'title' | 'label']: string }> = {
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.attackSurfaceReduction.type',
|
||||
{
|
||||
defaultMessage: 'Attack surface reduction',
|
||||
}
|
||||
),
|
||||
label: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.credentialHardening.toggle',
|
||||
{
|
||||
defaultMessage: 'Credential hardening',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const AttackSurfaceReductionForm = memo(() => {
|
||||
const credentialHardeningEnabled = usePolicyDetailsSelector(isCredentialHardeningEnabled);
|
||||
const dispatch = useDispatch();
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) =>
|
||||
dispatch({
|
||||
type: 'userChangedCredentialHardening',
|
||||
payload: {
|
||||
enabled: event.target.checked,
|
||||
},
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfigForm type={TRANSLATIONS.title} supportedOss={[OperatingSystem.WINDOWS]}>
|
||||
<EuiSwitch
|
||||
label={TRANSLATIONS.label}
|
||||
checked={credentialHardeningEnabled}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={!showEditableFormFields}
|
||||
/>
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
||||
AttackSurfaceReductionForm.displayName = 'AttackSurfaceReductionForm';
|
|
@ -1,67 +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 { ThemeProvider } from 'styled-components';
|
||||
import { addDecorator, storiesOf } from '@storybook/react';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import { EuiCheckbox, EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui';
|
||||
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
|
||||
import { ConfigForm } from '.';
|
||||
|
||||
addDecorator((storyFn) => (
|
||||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>{storyFn()}</ThemeProvider>
|
||||
));
|
||||
|
||||
storiesOf('PolicyDetails/ConfigForm', module)
|
||||
.add('One OS', () => {
|
||||
return (
|
||||
<ConfigForm type="Type 1" supportedOss={[OperatingSystem.WINDOWS]}>
|
||||
{'Some content'}
|
||||
</ConfigForm>
|
||||
);
|
||||
})
|
||||
.add('Multiple OSs', () => {
|
||||
return (
|
||||
<ConfigForm
|
||||
type="Type 1"
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
>
|
||||
{'Some content'}
|
||||
</ConfigForm>
|
||||
);
|
||||
})
|
||||
.add('Complex content', () => {
|
||||
return (
|
||||
<ConfigForm type="Type 1" supportedOss={[OperatingSystem.MAC, OperatingSystem.LINUX]}>
|
||||
<EuiText>
|
||||
{'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ' +
|
||||
'et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut ' +
|
||||
'aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum ' +
|
||||
'dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia ' +
|
||||
'deserunt mollit anim id est laborum.'}
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiSwitch label={'Switch'} checked={true} onChange={() => {}} />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCheckbox id="1" label={'Checkbox 1'} checked={false} onChange={() => {}} />
|
||||
<EuiCheckbox id="2" label={'Checkbox 2'} checked={true} onChange={() => {}} />
|
||||
<EuiCheckbox id="3" label={'Checkbox 3'} checked={true} onChange={() => {}} />
|
||||
</ConfigForm>
|
||||
);
|
||||
})
|
||||
.add('Right corner content', () => {
|
||||
const toggle = <EuiSwitch label={'Switch'} checked={true} onChange={() => {}} />;
|
||||
|
||||
return (
|
||||
<ConfigForm type="Type 1" supportedOss={[OperatingSystem.LINUX]} rightCorner={toggle}>
|
||||
{'Some content'}
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
|
@ -1,197 +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, { useContext, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiCheckbox,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
htmlIdGenerator,
|
||||
EuiIconTip,
|
||||
EuiBetaBadge,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
} from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import type {
|
||||
PolicyOperatingSystem,
|
||||
UIPolicyConfig,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import { ConfigForm, ConfigFormHeading } from '../config_form';
|
||||
|
||||
const OPERATING_SYSTEM_TO_TEST_SUBJ: { [K in OperatingSystem]: string } = {
|
||||
[OperatingSystem.WINDOWS]: 'Windows',
|
||||
[OperatingSystem.LINUX]: 'Linux',
|
||||
[OperatingSystem.MAC]: 'Mac',
|
||||
};
|
||||
|
||||
interface OperatingSystemToOsMap {
|
||||
[OperatingSystem.WINDOWS]: PolicyOperatingSystem.windows;
|
||||
[OperatingSystem.LINUX]: PolicyOperatingSystem.linux;
|
||||
[OperatingSystem.MAC]: PolicyOperatingSystem.mac;
|
||||
}
|
||||
|
||||
export type ProtectionField<T extends OperatingSystem> =
|
||||
keyof UIPolicyConfig[OperatingSystemToOsMap[T]]['events'];
|
||||
|
||||
export type EventFormSelection<T extends OperatingSystem> = { [K in ProtectionField<T>]: boolean };
|
||||
|
||||
export interface EventFormOption<T extends OperatingSystem> {
|
||||
name: string;
|
||||
protectionField: ProtectionField<T>;
|
||||
}
|
||||
|
||||
export interface SupplementalEventFormOption<T extends OperatingSystem> {
|
||||
title?: string;
|
||||
description?: string;
|
||||
name: string;
|
||||
protectionField: ProtectionField<T>;
|
||||
tooltipText?: string;
|
||||
beta?: boolean;
|
||||
indented?: boolean;
|
||||
isDisabled?(policyConfig: UIPolicyConfig): boolean;
|
||||
}
|
||||
|
||||
export interface EventsFormProps<T extends OperatingSystem> {
|
||||
os: T;
|
||||
options: ReadonlyArray<EventFormOption<T>>;
|
||||
selection: EventFormSelection<T>;
|
||||
onValueSelection: (value: ProtectionField<T>, selected: boolean) => void;
|
||||
supplementalOptions?: ReadonlyArray<SupplementalEventFormOption<T>>;
|
||||
}
|
||||
|
||||
const InnerEventsForm = <T extends OperatingSystem>({
|
||||
os,
|
||||
options,
|
||||
selection,
|
||||
onValueSelection,
|
||||
supplementalOptions,
|
||||
}: EventsFormProps<T>) => {
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const theme = useContext(ThemeContext);
|
||||
const countSelected = useCallback(() => {
|
||||
const supplementalSelectionFields: string[] = supplementalOptions
|
||||
? supplementalOptions.map((value) => value.protectionField as string)
|
||||
: [];
|
||||
return Object.entries(selection).filter(([key, value]) =>
|
||||
!supplementalSelectionFields.includes(key) ? value : false
|
||||
).length;
|
||||
}, [selection, supplementalOptions]);
|
||||
|
||||
return (
|
||||
<ConfigForm
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollection', {
|
||||
defaultMessage: 'Event collection',
|
||||
})}
|
||||
supportedOss={[os]}
|
||||
rightCorner={
|
||||
<EuiText size="s" color="subdued">
|
||||
{i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled',
|
||||
{
|
||||
defaultMessage: '{selected} / {total} event collections enabled',
|
||||
values: {
|
||||
selected: countSelected(),
|
||||
total: options.length,
|
||||
},
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
<ConfigFormHeading>
|
||||
{i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents', {
|
||||
defaultMessage: 'Events',
|
||||
})}
|
||||
</ConfigFormHeading>
|
||||
<EuiSpacer size="s" />
|
||||
{options.map(({ name, protectionField }) => {
|
||||
return (
|
||||
<EuiCheckbox
|
||||
key={String(protectionField)}
|
||||
id={htmlIdGenerator()()}
|
||||
label={name}
|
||||
data-test-subj={`policy${OPERATING_SYSTEM_TO_TEST_SUBJ[os]}Event_${protectionField}`}
|
||||
checked={selection[protectionField]}
|
||||
onChange={(event) => onValueSelection(protectionField, event.target.checked)}
|
||||
disabled={!showEditableFormFields}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{supplementalOptions &&
|
||||
supplementalOptions.map(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
name,
|
||||
protectionField,
|
||||
tooltipText,
|
||||
beta,
|
||||
indented,
|
||||
isDisabled,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
key={String(protectionField)}
|
||||
style={indented ? { paddingLeft: theme.eui.euiSizeL } : {}}
|
||||
>
|
||||
{title && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<ConfigFormHeading>{title}</ConfigFormHeading>
|
||||
</>
|
||||
)}
|
||||
{description && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
{description}
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
<EuiFlexGroup direction="row" gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCheckbox
|
||||
id={htmlIdGenerator()()}
|
||||
label={name}
|
||||
data-test-subj={`policy${OPERATING_SYSTEM_TO_TEST_SUBJ[os]}Event_${protectionField}`}
|
||||
checked={selection[protectionField]}
|
||||
onChange={(event) => onValueSelection(protectionField, event.target.checked)}
|
||||
disabled={
|
||||
!showEditableFormFields ||
|
||||
(isDisabled ? isDisabled(policyDetailsConfig) : false)
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{tooltipText && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip position="right" content={tooltipText} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{beta && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge label="beta" size="s" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ConfigForm>
|
||||
);
|
||||
};
|
||||
|
||||
InnerEventsForm.displayName = 'EventsForm';
|
||||
|
||||
export const EventsForm = React.memo(InnerEventsForm) as typeof InnerEventsForm;
|
|
@ -7,4 +7,3 @@
|
|||
|
||||
export * from './policy_list';
|
||||
export * from './policy_details';
|
||||
export * from './policy_advanced';
|
||||
|
|
|
@ -14,6 +14,8 @@ import type { AppContextTestRender } from '../../../../../../common/mock/endpoin
|
|||
import { EndpointPolicyEditExtension } from './endpoint_policy_edit_extension';
|
||||
import { createFleetContextRendererMock } from '../mocks';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../../../../../common/components/user_privileges/__mocks__';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy';
|
||||
|
||||
jest.mock('../../../../../../common/components/user_privileges');
|
||||
const useUserPrivilegesMock = useUserPrivileges as jest.Mock;
|
||||
|
@ -29,12 +31,16 @@ describe('When displaying the EndpointPolicyEditExtension fleet UI extension', (
|
|||
|
||||
beforeEach(() => {
|
||||
const mockedTestContext = createFleetContextRendererMock();
|
||||
const policy = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy({
|
||||
id: 'someid',
|
||||
});
|
||||
const newPolicy = getPolicyDataForUpdate(policy);
|
||||
|
||||
render = () =>
|
||||
mockedTestContext.render(
|
||||
<EndpointPolicyEditExtension
|
||||
policy={{ id: 'someid' } as PackagePolicy}
|
||||
newPolicy={{ id: 'someid' } as NewPackagePolicy}
|
||||
policy={policy as PackagePolicy}
|
||||
newPolicy={newPolicy as NewPackagePolicy}
|
||||
onChange={jest.fn()}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -5,136 +5,68 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { EuiCallOut, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import type {
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
NewPackagePolicy,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import type { PackagePolicyEditExtensionComponentProps } from '@kbn/fleet-plugin/public';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useUserPrivileges } from '../../../../../../common/components/user_privileges';
|
||||
import type { PolicySettingsFormProps } from '../../policy_settings_form/policy_settings_form';
|
||||
import type { NewPolicyData } from '../../../../../../../common/endpoint/types';
|
||||
import { EndpointPolicyArtifactCards } from './components/endpoint_policy_artifact_cards';
|
||||
import { getPolicyDetailPath } from '../../../../../common/routing';
|
||||
import { PolicyDetailsForm } from '../../policy_details_form';
|
||||
import type { AppAction } from '../../../../../../common/store/actions';
|
||||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import {
|
||||
apiError,
|
||||
policyDetails,
|
||||
policyDetailsForUpdate,
|
||||
} from '../../../store/policy_details/selectors';
|
||||
import { PolicySettingsForm } from '../../policy_settings_form';
|
||||
|
||||
/**
|
||||
* Exports Endpoint-specific package policy instructions
|
||||
* for use in the Ingest app create / edit package policy
|
||||
*/
|
||||
export const EndpointPolicyEditExtension = memo<PackagePolicyEditExtensionComponentProps>(
|
||||
({ policy, onChange }) => {
|
||||
({ policy, onChange, newPolicy: _newPolicy }) => {
|
||||
const policyUpdates = _newPolicy as NewPolicyData;
|
||||
const endpointPolicySettings = policyUpdates.inputs[0].config.policy.value;
|
||||
const { canAccessFleet } = useUserPrivileges().endpointPrivileges;
|
||||
|
||||
const endpointPolicySettingsOnChangeHandler: PolicySettingsFormProps['onChange'] = useCallback(
|
||||
({ isValid, updatedPolicy }) => {
|
||||
const newPolicyInputs = cloneDeep(policyUpdates.inputs);
|
||||
newPolicyInputs[0].config.policy.value = updatedPolicy;
|
||||
|
||||
onChange({
|
||||
isValid,
|
||||
updatedPolicy: { inputs: newPolicyInputs },
|
||||
});
|
||||
},
|
||||
[onChange, policyUpdates.inputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<WrappedPolicyDetailsForm policyId={policy.id} onChange={onChange} />
|
||||
<div data-test-subj="endpointIntegrationPolicyForm">
|
||||
<EndpointPolicyArtifactCards policyId={policy.id} />
|
||||
|
||||
<div>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.settings.title"
|
||||
defaultMessage="Policy settings"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<PolicySettingsForm
|
||||
policy={endpointPolicySettings}
|
||||
onChange={endpointPolicySettingsOnChangeHandler}
|
||||
mode={canAccessFleet ? 'edit' : 'view'}
|
||||
data-test-subj="endpointPolicyForm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
EndpointPolicyEditExtension.displayName = 'EndpointPolicyEditExtension';
|
||||
|
||||
const WrappedPolicyDetailsForm = memo<{
|
||||
policyId: string;
|
||||
onChange: PackagePolicyEditExtensionComponentProps['onChange'];
|
||||
}>(({ policyId, onChange }) => {
|
||||
const dispatch = useDispatch<(a: AppAction) => void>();
|
||||
const updatedPolicy = usePolicyDetailsSelector(policyDetailsForUpdate);
|
||||
const endpointPolicyDetails = usePolicyDetailsSelector(policyDetails);
|
||||
const endpointDetailsLoadingError = usePolicyDetailsSelector(apiError);
|
||||
const [, setLastUpdatedPolicy] = useState(updatedPolicy);
|
||||
|
||||
// When the form is initially displayed, trigger the Redux middleware which is based on
|
||||
// the location information stored via the `userChangedUrl` action.
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'userChangedUrl',
|
||||
payload: {
|
||||
hash: '',
|
||||
pathname: getPolicyDetailPath(policyId, ''),
|
||||
search: '',
|
||||
},
|
||||
});
|
||||
|
||||
// When form is unloaded, reset the redux store
|
||||
return () => {
|
||||
dispatch({
|
||||
type: 'userChangedUrl',
|
||||
payload: {
|
||||
hash: '',
|
||||
pathname: '/',
|
||||
search: '',
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [dispatch, policyId]);
|
||||
|
||||
useEffect(() => {
|
||||
// Currently, the `onChange` callback provided by the fleet UI extension is regenerated every
|
||||
// time the policy data is updated, which means this will go into a continuous loop if we don't
|
||||
// actually check to see if an update should be reported back to fleet
|
||||
setLastUpdatedPolicy((prevState) => {
|
||||
if (prevState === updatedPolicy) {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
if (updatedPolicy) {
|
||||
onChange({
|
||||
isValid: true,
|
||||
// send up only the updated policy data which is stored in the `inputs` section.
|
||||
// All other attributes (like name, id) are updated from the Fleet form, so we want to
|
||||
// ensure we don't override it.
|
||||
updatedPolicy: {
|
||||
// Casting is needed due to the use of `Immutable<>` in our store data
|
||||
inputs: updatedPolicy.inputs as unknown as NewPackagePolicy['inputs'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return updatedPolicy;
|
||||
});
|
||||
}, [onChange, updatedPolicy]);
|
||||
|
||||
return (
|
||||
<div data-test-subj="endpointIntegrationPolicyForm">
|
||||
<EndpointPolicyArtifactCards policyId={policyId} />
|
||||
<div>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.settings.title"
|
||||
defaultMessage="Policy settings"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
{endpointDetailsLoadingError ? (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.loadError"
|
||||
defaultMessage="Failed to load endpoint policy settings"
|
||||
/>
|
||||
}
|
||||
iconType="warning"
|
||||
color="warning"
|
||||
data-test-subj="endpiontPolicySettingsLoadingError"
|
||||
>
|
||||
{endpointDetailsLoadingError.message}
|
||||
</EuiCallOut>
|
||||
) : !endpointPolicyDetails ? (
|
||||
<EuiLoadingSpinner size="l" className="essentialAnimation" />
|
||||
) : (
|
||||
<PolicyDetailsForm />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
WrappedPolicyDetailsForm.displayName = 'WrappedPolicyDetailsForm';
|
||||
|
|
|
@ -1,213 +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, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIconTip,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { policyConfig } from '../store/policy_details/selectors';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from './policy_hooks';
|
||||
import { AdvancedPolicySchema } from '../models/advanced_policy_schema';
|
||||
|
||||
function setValue(obj: Record<string, unknown>, value: string, path: string[]) {
|
||||
let newPolicyConfig = obj;
|
||||
|
||||
// First set the value.
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
if (!newPolicyConfig[path[i]]) {
|
||||
newPolicyConfig[path[i]] = {} as Record<string, unknown>;
|
||||
}
|
||||
newPolicyConfig = newPolicyConfig[path[i]] as Record<string, unknown>;
|
||||
}
|
||||
newPolicyConfig[path[path.length - 1]] = value;
|
||||
|
||||
// Then, if the user is deleting the value, we need to ensure we clean up the config.
|
||||
// We delete any sections that are empty, whether that be an empty string, empty object, or undefined.
|
||||
if (value === '' || value === undefined) {
|
||||
newPolicyConfig = obj;
|
||||
for (let k = path.length; k >= 0; k--) {
|
||||
const nextPath = path.slice(0, k);
|
||||
for (let i = 0; i < nextPath.length - 1; i++) {
|
||||
// Traverse and find the next section
|
||||
newPolicyConfig = newPolicyConfig[nextPath[i]] as Record<string, unknown>;
|
||||
}
|
||||
if (
|
||||
newPolicyConfig[nextPath[nextPath.length - 1]] === undefined ||
|
||||
newPolicyConfig[nextPath[nextPath.length - 1]] === '' ||
|
||||
Object.keys(newPolicyConfig[nextPath[nextPath.length - 1]] as object).length === 0
|
||||
) {
|
||||
// If we're looking at the `advanced` field, we leave it undefined as opposed to deleting it.
|
||||
// This is because the UI looks for this field to begin rendering.
|
||||
if (nextPath[nextPath.length - 1] === 'advanced') {
|
||||
newPolicyConfig[nextPath[nextPath.length - 1]] = undefined;
|
||||
// In all other cases, if field is empty, we'll delete it to clean up.
|
||||
} else {
|
||||
delete newPolicyConfig[nextPath[nextPath.length - 1]];
|
||||
}
|
||||
newPolicyConfig = obj;
|
||||
} else {
|
||||
break; // We are looking at a non-empty section, so we can terminate.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(obj: Record<string, unknown>, path: string[]) {
|
||||
let currentPolicyConfig = obj;
|
||||
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
if (currentPolicyConfig[path[i]]) {
|
||||
currentPolicyConfig = currentPolicyConfig[path[i]] as Record<string, unknown>;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return currentPolicyConfig[path[path.length - 1]];
|
||||
}
|
||||
const calloutTitle = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.advanced.calloutTitle',
|
||||
{
|
||||
defaultMessage: 'Proceed with caution!',
|
||||
}
|
||||
);
|
||||
const warningMessage = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.advanced.warningMessage',
|
||||
{
|
||||
defaultMessage: `This section contains policy values that support advanced use cases. If not configured
|
||||
properly, these values can cause unpredictable behavior. Please consult documentation
|
||||
carefully or contact support before editing these values.`,
|
||||
}
|
||||
);
|
||||
|
||||
export const AdvancedPolicyForms = React.memo(({ isPlatinumPlus }: { isPlatinumPlus: boolean }) => {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={calloutTitle}
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
data-test-subj="policyAdvancedSettingsWarning"
|
||||
>
|
||||
<p>{warningMessage}</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
<EuiText size="xs" color="subdued">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.advanced"
|
||||
defaultMessage="Advanced settings"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
<EuiPanel data-test-subj="advancedPolicyPanel" paddingSize="s">
|
||||
{AdvancedPolicySchema.map((advancedField, index) => {
|
||||
const configPath = advancedField.key.split('.');
|
||||
const failsPlatinumLicenseCheck = !isPlatinumPlus && advancedField.license === 'platinum';
|
||||
return (
|
||||
!failsPlatinumLicenseCheck && (
|
||||
<PolicyAdvanced
|
||||
key={index}
|
||||
configPath={configPath}
|
||||
firstSupportedVersion={advancedField.first_supported_version}
|
||||
lastSupportedVersion={advancedField.last_supported_version}
|
||||
documentation={advancedField.documentation}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
AdvancedPolicyForms.displayName = 'AdvancedPolicyForms';
|
||||
|
||||
const PolicyAdvanced = React.memo(
|
||||
({
|
||||
configPath,
|
||||
firstSupportedVersion,
|
||||
lastSupportedVersion,
|
||||
documentation,
|
||||
}: {
|
||||
configPath: string[];
|
||||
firstSupportedVersion: string;
|
||||
lastSupportedVersion?: string;
|
||||
documentation: string;
|
||||
}) => {
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
const dispatch = useDispatch();
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const onChange = useCallback(
|
||||
(event) => {
|
||||
if (policyDetailsConfig) {
|
||||
const newPayload = cloneDeep(policyDetailsConfig);
|
||||
setValue(
|
||||
newPayload as unknown as Record<string, unknown>,
|
||||
event.target.value,
|
||||
configPath
|
||||
);
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: newPayload },
|
||||
});
|
||||
}
|
||||
},
|
||||
[dispatch, policyDetailsConfig, configPath]
|
||||
);
|
||||
|
||||
const value =
|
||||
policyDetailsConfig &&
|
||||
getValue(policyDetailsConfig as unknown as Record<string, unknown>, configPath);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem grow={true}>{configPath.join('.')}</EuiFlexItem>
|
||||
{documentation && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip content={documentation} position="right" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs">
|
||||
{lastSupportedVersion
|
||||
? `${firstSupportedVersion}-${lastSupportedVersion}`
|
||||
: `${firstSupportedVersion}+`}
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj={configPath.join('.')}
|
||||
fullWidth
|
||||
value={value as string}
|
||||
onChange={onChange}
|
||||
disabled={!showEditableFormFields}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
PolicyAdvanced.displayName = 'PolicyAdvanced';
|
|
@ -28,9 +28,6 @@ import { policyListApiPathHandlers } from '../store/test_mock_utils';
|
|||
import { PolicyDetails } from './policy_details';
|
||||
import { APP_UI_ID } from '../../../../../common/constants';
|
||||
|
||||
jest.mock('./policy_forms/components/policy_form_layout', () => ({
|
||||
PolicyFormLayout: () => <></>,
|
||||
}));
|
||||
jest.mock('../../../../common/components/user_privileges');
|
||||
|
||||
const useUserPrivilegesMock = useUserPrivileges as jest.Mock;
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { usePolicyDetailsSelector } from './policy_hooks';
|
||||
import { policyDetails, agentStatusSummary, apiError } from '../store/policy_details/selectors';
|
||||
import { AgentsSummary } from './agents_summary';
|
||||
import { AgentsSummary } from './components/agents_summary';
|
||||
import { PolicyTabs } from './tabs';
|
||||
import { AdministrationListPage } from '../../../components/administration_list_page';
|
||||
import type { BackToExternalAppButtonProps } from '../../../components/back_to_external_app_button/back_to_external_app_button';
|
||||
|
|
|
@ -1,131 +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 { EuiButtonEmpty, EuiSkeletonText, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React, { memo, useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
import { MalwareProtections } from './policy_forms/protections/malware';
|
||||
import { MemoryProtection } from './policy_forms/protections/memory';
|
||||
import { BehaviorProtection } from './policy_forms/protections/behavior';
|
||||
import { LinuxEvents, MacEvents, WindowsEvents } from './policy_forms/events';
|
||||
import { AdvancedPolicyForms } from './policy_advanced';
|
||||
import { AntivirusRegistrationForm } from './components/antivirus_registration_form';
|
||||
import { AttackSurfaceReductionForm } from './components/attack_surface_reduction_form';
|
||||
import { Ransomware } from './policy_forms/protections/ransomware';
|
||||
import { LockedPolicyCard } from './policy_forms/locked_card';
|
||||
import { useLicense } from '../../../../common/hooks/use_license';
|
||||
|
||||
const LOCKED_CARD_RAMSOMWARE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.ransomware',
|
||||
{
|
||||
defaultMessage: 'Ransomware',
|
||||
}
|
||||
);
|
||||
|
||||
const LOCKED_CARD_MEMORY_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.memory',
|
||||
{
|
||||
defaultMessage: 'Memory Threat',
|
||||
}
|
||||
);
|
||||
|
||||
const LOCKED_CARD_BEHAVIOR_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.behavior',
|
||||
{
|
||||
defaultMessage: 'Malicious Behavior',
|
||||
}
|
||||
);
|
||||
|
||||
const LOCKED_CARD_ATTACK_SURFACE_REDUCTION = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.attack_surface_reduction',
|
||||
{
|
||||
defaultMessage: 'Attack Surface Reduction',
|
||||
}
|
||||
);
|
||||
|
||||
export const PolicyDetailsForm = memo(() => {
|
||||
const [showAdvancedPolicy, setShowAdvancedPolicy] = useState<boolean>(false);
|
||||
const handleAdvancedPolicyClick = useCallback(() => {
|
||||
setShowAdvancedPolicy(!showAdvancedPolicy);
|
||||
}, [showAdvancedPolicy]);
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const { loading: authzLoading } = useUserPrivileges().endpointPrivileges;
|
||||
|
||||
if (authzLoading) {
|
||||
return <EuiSkeletonText lines={5} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText size="xs" color="subdued">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.protections"
|
||||
defaultMessage="Protections"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<MalwareProtections />
|
||||
<EuiSpacer size="l" />
|
||||
{isPlatinumPlus ? <Ransomware /> : <LockedPolicyCard title={LOCKED_CARD_RAMSOMWARE_TITLE} />}
|
||||
<EuiSpacer size="l" />
|
||||
{isPlatinumPlus ? (
|
||||
<MemoryProtection />
|
||||
) : (
|
||||
<LockedPolicyCard title={LOCKED_CARD_MEMORY_TITLE} />
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
{isPlatinumPlus ? (
|
||||
<BehaviorProtection />
|
||||
) : (
|
||||
<LockedPolicyCard title={LOCKED_CARD_BEHAVIOR_TITLE} />
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
{isPlatinumPlus ? (
|
||||
<AttackSurfaceReductionForm />
|
||||
) : (
|
||||
<LockedPolicyCard title={LOCKED_CARD_ATTACK_SURFACE_REDUCTION} />
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiText size="xs" color="subdued">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.settings"
|
||||
defaultMessage="Settings"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<WindowsEvents />
|
||||
<EuiSpacer size="l" />
|
||||
<MacEvents />
|
||||
<EuiSpacer size="l" />
|
||||
<LinuxEvents />
|
||||
<EuiSpacer size="l" />
|
||||
<AntivirusRegistrationForm />
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButtonEmpty data-test-subj="advancedPolicyButton" onClick={handleAdvancedPolicyClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.advanced.show"
|
||||
defaultMessage="{action} advanced settings"
|
||||
values={{ action: showAdvancedPolicy ? 'Hide' : 'Show' }}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
{showAdvancedPolicy && <AdvancedPolicyForms isPlatinumPlus={isPlatinumPlus} />}
|
||||
</>
|
||||
);
|
||||
});
|
||||
PolicyDetailsForm.displayName = 'PolicyDetailsForm';
|
|
@ -1,470 +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 type { ReactWrapper } from 'enzyme';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { PolicyFormLayout } from './policy_form_layout';
|
||||
import '../../../../../../common/mock/match_media';
|
||||
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import {
|
||||
createAppRootMockRenderer,
|
||||
resetReactDomCreatePortalMock,
|
||||
} from '../../../../../../common/mock/endpoint';
|
||||
import { getPolicyDetailPath, getPoliciesPath } from '../../../../../common/routing';
|
||||
import { policyListApiPathHandlers } from '../../../store/test_mock_utils';
|
||||
import { licenseService } from '../../../../../../common/hooks/use_license';
|
||||
import { PACKAGE_POLICY_API_ROOT, AGENT_API_ROUTES } from '@kbn/fleet-plugin/common';
|
||||
import { useUserPrivileges as _useUserPrivileges } from '../../../../../../common/components/user_privileges';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../../../../../common/components/user_privileges/__mocks__';
|
||||
|
||||
jest.mock('../../../../../../common/hooks/use_license');
|
||||
jest.mock('../../../../../../common/components/user_privileges');
|
||||
|
||||
const useUserPrivilegesMock = _useUserPrivileges as jest.Mock;
|
||||
|
||||
describe('Policy Form Layout', () => {
|
||||
type FindReactWrapperResponse = ReturnType<ReturnType<typeof render>['find']>;
|
||||
|
||||
const policyDetailsPathUrl = getPolicyDetailPath('1');
|
||||
const policyListPath = getPoliciesPath();
|
||||
const sleep = (ms = 100) => new Promise((wakeup) => setTimeout(wakeup, ms));
|
||||
const generator = new EndpointDocGenerator();
|
||||
let history: AppContextTestRender['history'];
|
||||
let coreStart: AppContextTestRender['coreStart'];
|
||||
let http: typeof coreStart.http;
|
||||
let render: (ui: Parameters<typeof mount>[0]) => ReturnType<typeof mount>;
|
||||
let policyPackagePolicy: ReturnType<typeof generator.generatePolicyPackagePolicy>;
|
||||
let policyFormLayoutView: ReturnType<typeof render>;
|
||||
|
||||
beforeAll(() => resetReactDomCreatePortalMock());
|
||||
|
||||
beforeEach(() => {
|
||||
const appContextMockRenderer = createAppRootMockRenderer();
|
||||
const AppWrapper = appContextMockRenderer.AppWrapper;
|
||||
|
||||
({ history, coreStart } = appContextMockRenderer);
|
||||
render = (ui) => mount(ui, { wrappingComponent: AppWrapper });
|
||||
http = coreStart.http;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (policyFormLayoutView) {
|
||||
policyFormLayoutView.unmount();
|
||||
}
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when displayed with valid id', () => {
|
||||
let asyncActions: Promise<unknown> = Promise.resolve();
|
||||
|
||||
beforeEach(async () => {
|
||||
policyPackagePolicy = generator.generatePolicyPackagePolicy();
|
||||
policyPackagePolicy.id = '1';
|
||||
|
||||
const policyListApiHandlers = policyListApiPathHandlers();
|
||||
|
||||
http.get.mockImplementation((...args) => {
|
||||
const [path] = args;
|
||||
if (typeof path === 'string') {
|
||||
// GET datasouce
|
||||
if (path === `${PACKAGE_POLICY_API_ROOT}/1`) {
|
||||
asyncActions = asyncActions.then<unknown>(async (): Promise<unknown> => sleep());
|
||||
return Promise.resolve({
|
||||
item: policyPackagePolicy,
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
|
||||
// GET Agent status for agent policy
|
||||
if (path === `${AGENT_API_ROUTES.STATUS_PATTERN}`) {
|
||||
asyncActions = asyncActions.then(async () => sleep());
|
||||
return Promise.resolve({
|
||||
results: { events: 0, total: 5, online: 3, error: 1, offline: 1 },
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Get package data
|
||||
// Used in tests that route back to the list
|
||||
if (policyListApiHandlers[path]) {
|
||||
asyncActions = asyncActions.then(async () => sleep());
|
||||
return Promise.resolve(policyListApiHandlers[path]());
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`unknown API call (not MOCKED): ${path}`));
|
||||
});
|
||||
history.push(policyDetailsPathUrl);
|
||||
policyFormLayoutView = render(<PolicyFormLayout />);
|
||||
|
||||
await asyncActions;
|
||||
policyFormLayoutView.update();
|
||||
});
|
||||
|
||||
it('should NOT display timeline', async () => {
|
||||
expect(policyFormLayoutView.find('flyoutOverlay')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should display cancel button', async () => {
|
||||
const cancelbutton = policyFormLayoutView.find(
|
||||
'EuiButtonEmpty[data-test-subj="policyDetailsCancelButton"]'
|
||||
);
|
||||
expect(cancelbutton).toHaveLength(1);
|
||||
expect(cancelbutton.text()).toEqual('Cancel');
|
||||
});
|
||||
it('should redirect to policy list when cancel button is clicked', async () => {
|
||||
const cancelbutton = policyFormLayoutView.find(
|
||||
'EuiButtonEmpty[data-test-subj="policyDetailsCancelButton"]'
|
||||
);
|
||||
expect(history.location.pathname).toEqual(policyDetailsPathUrl);
|
||||
cancelbutton.simulate('click', { button: 0 });
|
||||
const navigateToAppMockedCalls = coreStart.application.navigateToApp.mock.calls;
|
||||
expect(navigateToAppMockedCalls[navigateToAppMockedCalls.length - 1]).toEqual([
|
||||
'securitySolutionUI',
|
||||
{ path: policyListPath },
|
||||
]);
|
||||
});
|
||||
it('should display save button', async () => {
|
||||
const saveButton = policyFormLayoutView.find(
|
||||
'EuiButton[data-test-subj="policyDetailsSaveButton"]'
|
||||
);
|
||||
expect(saveButton).toHaveLength(1);
|
||||
expect(saveButton.text()).toEqual('Save');
|
||||
});
|
||||
it('should display beta badge', async () => {
|
||||
const saveButton = policyFormLayoutView.find('EuiBetaBadge');
|
||||
expect(saveButton).toHaveLength(1);
|
||||
expect(saveButton.text()).toEqual('beta');
|
||||
});
|
||||
|
||||
it('should display minimum Agent version number for User Notification', async () => {
|
||||
const minVersionsMap = [
|
||||
['malware', '7.11'],
|
||||
['ransomware', '7.12'],
|
||||
['behavior', '7.15'],
|
||||
['memory', '7.15'],
|
||||
];
|
||||
|
||||
for (const [protection, minVersion] of minVersionsMap) {
|
||||
expect(
|
||||
policyFormLayoutView
|
||||
.find(`EuiPanel[data-test-subj="${protection}ProtectionsForm"]`)
|
||||
.find('EuiText[data-test-subj="policySupportedVersions"]')
|
||||
.text()
|
||||
).toEqual(`Agent version ${minVersion}+`);
|
||||
}
|
||||
});
|
||||
|
||||
it('"Register as antivirus" should be only available for Windows', () => {
|
||||
const antivirusRegistrationFormTextContent = policyFormLayoutView
|
||||
.find('EuiPanel[data-test-subj="antivirusRegistrationForm"]')
|
||||
.text();
|
||||
|
||||
expect(antivirusRegistrationFormTextContent).toContain('Windows');
|
||||
expect(antivirusRegistrationFormTextContent).not.toContain('Linux');
|
||||
expect(antivirusRegistrationFormTextContent).not.toContain('Mac');
|
||||
|
||||
expect(antivirusRegistrationFormTextContent).toContain(
|
||||
'Toggle on to register Elastic as an official Antivirus solution for Windows OS. This will also disable Windows Defender.'
|
||||
);
|
||||
});
|
||||
|
||||
describe('Advanced settings', () => {
|
||||
let showHideAdvancedSettingsButton: ReactWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
showHideAdvancedSettingsButton = policyFormLayoutView.find(
|
||||
'EuiButtonEmpty[data-test-subj="advancedPolicyButton"]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display "Show advanced settings" button, and hide advanced options on default', () => {
|
||||
expect(showHideAdvancedSettingsButton.text()).toEqual('Show advanced settings');
|
||||
expect(
|
||||
policyFormLayoutView.find('EuiPanel[data-test-subj="advancedPolicyPanel"]').length
|
||||
).toEqual(0);
|
||||
});
|
||||
|
||||
it('clicking on "Show/Hide advanced settings" should show/hide advanced settings', () => {
|
||||
showHideAdvancedSettingsButton.simulate('click');
|
||||
|
||||
expect(showHideAdvancedSettingsButton.text()).toEqual('Hide advanced settings');
|
||||
expect(
|
||||
policyFormLayoutView.find('EuiPanel[data-test-subj="advancedPolicyPanel"]').length
|
||||
).toEqual(1);
|
||||
|
||||
showHideAdvancedSettingsButton.simulate('click');
|
||||
|
||||
expect(
|
||||
policyFormLayoutView.find('EuiPanel[data-test-subj="advancedPolicyPanel"]').length
|
||||
).toEqual(0);
|
||||
});
|
||||
|
||||
it('should display a warning message', () => {
|
||||
showHideAdvancedSettingsButton.simulate('click');
|
||||
|
||||
expect(
|
||||
policyFormLayoutView.find('div[data-test-subj="policyAdvancedSettingsWarning"]').text()
|
||||
).toContain(
|
||||
`This section contains policy values that support advanced use cases. If not configured
|
||||
properly, these values can cause unpredictable behavior. Please consult documentation
|
||||
carefully or contact support before editing these values.`
|
||||
);
|
||||
});
|
||||
|
||||
it('every row should contain a tooltip', () => {
|
||||
showHideAdvancedSettingsButton.simulate('click');
|
||||
|
||||
policyFormLayoutView
|
||||
.find('EuiPanel[data-test-subj="advancedPolicyPanel"]')
|
||||
.find('EuiFormRow')
|
||||
.forEach((row) => {
|
||||
expect(row.find('EuiIconTip').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the save button is clicked', () => {
|
||||
let saveButton: FindReactWrapperResponse;
|
||||
let confirmModal: FindReactWrapperResponse;
|
||||
let modalCancelButton: FindReactWrapperResponse;
|
||||
let modalConfirmButton: FindReactWrapperResponse;
|
||||
|
||||
beforeEach(async () => {
|
||||
await asyncActions;
|
||||
policyFormLayoutView.update();
|
||||
saveButton = policyFormLayoutView.find('button[data-test-subj="policyDetailsSaveButton"]');
|
||||
saveButton.simulate('click');
|
||||
policyFormLayoutView.update();
|
||||
confirmModal = policyFormLayoutView.find(
|
||||
'EuiConfirmModal[data-test-subj="policyDetailsConfirmModal"]'
|
||||
);
|
||||
modalCancelButton = confirmModal.find('button[data-test-subj="confirmModalCancelButton"]');
|
||||
modalConfirmButton = confirmModal.find(
|
||||
'button[data-test-subj="confirmModalConfirmButton"]'
|
||||
);
|
||||
http.put.mockImplementation((...args) => {
|
||||
asyncActions = asyncActions.then(async () => sleep());
|
||||
const [path] = args;
|
||||
if (typeof path === 'string') {
|
||||
if (path === `${PACKAGE_POLICY_API_ROOT}/1`) {
|
||||
return Promise.resolve({
|
||||
item: policyPackagePolicy,
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('unknown PUT path!'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should show a modal confirmation', () => {
|
||||
expect(confirmModal).toHaveLength(1);
|
||||
expect(
|
||||
confirmModal.find('[data-test-subj="confirmModalTitleText"]').first().text()
|
||||
).toEqual('Save and deploy changes');
|
||||
expect(modalCancelButton.text()).toEqual('Cancel');
|
||||
expect(modalConfirmButton.text()).toEqual('Save and deploy changes');
|
||||
});
|
||||
it('should show info callout if policy is in use', () => {
|
||||
const warningCallout = confirmModal.find(
|
||||
'EuiCallOut[data-test-subj="policyDetailsWarningCallout"]'
|
||||
);
|
||||
expect(warningCallout).toHaveLength(1);
|
||||
expect(warningCallout.text()).toEqual(
|
||||
'This action will update 5 endpointsSaving these changes will apply updates to all endpoints assigned to this agent policy.'
|
||||
);
|
||||
});
|
||||
it('should close dialog if cancel button is clicked', () => {
|
||||
modalCancelButton.simulate('click');
|
||||
expect(
|
||||
policyFormLayoutView.find('EuiConfirmModal[data-test-subj="policyDetailsConfirmModal"]')
|
||||
).toHaveLength(0);
|
||||
});
|
||||
it('should update policy and show success notification when confirm button is clicked', async () => {
|
||||
modalConfirmButton.simulate('click');
|
||||
policyFormLayoutView.update();
|
||||
// Modal should be closed
|
||||
expect(
|
||||
policyFormLayoutView.find('EuiConfirmModal[data-test-subj="policyDetailsConfirmModal"]')
|
||||
).toHaveLength(0);
|
||||
|
||||
// API should be called
|
||||
await asyncActions;
|
||||
expect(http.put.mock.calls[0][0]).toEqual(`${PACKAGE_POLICY_API_ROOT}/1`);
|
||||
policyFormLayoutView.update();
|
||||
|
||||
// Toast notification should be shown
|
||||
const toastAddMock = coreStart.notifications.toasts.addSuccess.mock;
|
||||
expect(toastAddMock.calls).toHaveLength(1);
|
||||
expect(toastAddMock.calls[0][0]).toMatchObject({
|
||||
title: 'Success!',
|
||||
text: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should show an error notification toast if update fails', async () => {
|
||||
policyPackagePolicy.id = 'invalid';
|
||||
modalConfirmButton.simulate('click');
|
||||
|
||||
await asyncActions;
|
||||
policyFormLayoutView.update();
|
||||
|
||||
// Toast notification should be shown
|
||||
const toastAddMock = coreStart.notifications.toasts.addDanger.mock;
|
||||
expect(toastAddMock.calls).toHaveLength(1);
|
||||
expect(toastAddMock.calls[0][0]).toMatchObject({
|
||||
title: 'Failed!',
|
||||
text: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the subscription tier is platinum or higher', () => {
|
||||
beforeEach(() => {
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(true);
|
||||
policyFormLayoutView = render(<PolicyFormLayout />);
|
||||
});
|
||||
|
||||
it('malware popup, message customization options and tooltip are shown', () => {
|
||||
// use query for finding stuff, if it doesn't find it, just returns null
|
||||
const userNotificationCheckbox = policyFormLayoutView.find(
|
||||
'EuiCheckbox[data-test-subj="malwareUserNotificationCheckbox"]'
|
||||
);
|
||||
const userNotificationCustomMessageTextArea = policyFormLayoutView.find(
|
||||
'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]'
|
||||
);
|
||||
const tooltip = policyFormLayoutView.find('EuiIconTip[data-test-subj="malwareTooltip"]');
|
||||
expect(userNotificationCheckbox).toHaveLength(1);
|
||||
expect(userNotificationCustomMessageTextArea).toHaveLength(1);
|
||||
expect(tooltip).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('memory protection card and user notification checkbox are shown', () => {
|
||||
const memory = policyFormLayoutView.find(
|
||||
'EuiPanel[data-test-subj="memoryProtectionsForm"]'
|
||||
);
|
||||
const userNotificationCheckbox = policyFormLayoutView.find(
|
||||
'EuiCheckbox[data-test-subj="memory_protectionUserNotificationCheckbox"]'
|
||||
);
|
||||
|
||||
expect(memory).toHaveLength(1);
|
||||
expect(userNotificationCheckbox).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('behavior protection card and user notification checkbox are shown', () => {
|
||||
const behavior = policyFormLayoutView.find(
|
||||
'EuiPanel[data-test-subj="behaviorProtectionsForm"]'
|
||||
);
|
||||
const userNotificationCheckbox = policyFormLayoutView.find(
|
||||
'EuiCheckbox[data-test-subj="behavior_protectionUserNotificationCheckbox"]'
|
||||
);
|
||||
|
||||
expect(behavior).toHaveLength(1);
|
||||
expect(userNotificationCheckbox).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('ransomware card is shown', () => {
|
||||
const ransomware = policyFormLayoutView.find(
|
||||
'EuiPanel[data-test-subj="ransomwareProtectionsForm"]'
|
||||
);
|
||||
expect(ransomware).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the subscription tier is gold or lower', () => {
|
||||
beforeEach(() => {
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
|
||||
policyFormLayoutView = render(<PolicyFormLayout />);
|
||||
});
|
||||
|
||||
it('malware popup, message customization options, and tooltip are hidden', () => {
|
||||
const userNotificationCheckbox = policyFormLayoutView.find(
|
||||
'EuiCheckbox[data-test-subj="malwareUserNotificationCheckbox"]'
|
||||
);
|
||||
const userNotificationCustomMessageTextArea = policyFormLayoutView.find(
|
||||
'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]'
|
||||
);
|
||||
const tooltip = policyFormLayoutView.find('EuiIconTip[data-test-subj="malwareTooltip"]');
|
||||
expect(userNotificationCheckbox).toHaveLength(0);
|
||||
expect(userNotificationCustomMessageTextArea).toHaveLength(0);
|
||||
expect(tooltip).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('memory protection card, and user notification checkbox are hidden', () => {
|
||||
const memory = policyFormLayoutView.find(
|
||||
'EuiPanel[data-test-subj="memoryProtectionsForm"]'
|
||||
);
|
||||
expect(memory).toHaveLength(0);
|
||||
const userNotificationCheckbox = policyFormLayoutView.find(
|
||||
'EuiCheckbox[data-test-subj="memoryUserNotificationCheckbox"]'
|
||||
);
|
||||
expect(userNotificationCheckbox).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('ransomware card is hidden', () => {
|
||||
const ransomware = policyFormLayoutView.find(
|
||||
'EuiPanel[data-test-subj="ransomwareProtectionsForm"]'
|
||||
);
|
||||
expect(ransomware).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('shows the locked card in place of paid features', () => {
|
||||
const lockedCard = policyFormLayoutView.find('EuiCard[data-test-subj="lockedPolicyCard"]');
|
||||
expect(lockedCard).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('locked card has "Upgrade now" link to cloud server', () => {
|
||||
const upgradeLinks = policyFormLayoutView.find(
|
||||
'EuiLink[data-test-subj="upgradeNowCloudDeploymentLink"]'
|
||||
);
|
||||
upgradeLinks.forEach((link) => {
|
||||
expect(link.prop('href')).toEqual('https://www.elastic.co/cloud/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and user has only READ privilege', () => {
|
||||
beforeEach(() => {
|
||||
const mockedPrivileges = getUserPrivilegesMockDefaultValue();
|
||||
mockedPrivileges.endpointPrivileges.canWritePolicyManagement = false;
|
||||
mockedPrivileges.endpointPrivileges.canAccessFleet = false;
|
||||
|
||||
useUserPrivilegesMock.mockReturnValue(mockedPrivileges);
|
||||
|
||||
policyFormLayoutView = render(<PolicyFormLayout />);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useUserPrivilegesMock.mockImplementation(getUserPrivilegesMockDefaultValue);
|
||||
});
|
||||
|
||||
it('should not display the Save button', () => {
|
||||
expect(
|
||||
policyFormLayoutView.find('EuiButton[data-test-subj="policyDetailsSaveButton"]')
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should display all form controls as disabled', () => {
|
||||
policyFormLayoutView
|
||||
.find('button[data-test-subj="advancedPolicyButton"]')
|
||||
.simulate('click');
|
||||
|
||||
const inputElements = policyFormLayoutView.find('input');
|
||||
|
||||
expect(inputElements.length).toBeGreaterThan(0);
|
||||
|
||||
inputElements.forEach((element) => {
|
||||
expect(element.prop('disabled')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,98 +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, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { htmlIdGenerator, EuiRadio } from '@elastic/eui';
|
||||
import type { ImmutableArray, UIPolicyConfig } from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { MacPolicyProtection, LinuxPolicyProtection, PolicyProtection } from '../../../types';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import type { AppAction } from '../../../../../../common/store/actions';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
|
||||
export const ProtectionRadio = React.memo(
|
||||
({
|
||||
protection,
|
||||
protectionMode,
|
||||
osList,
|
||||
label,
|
||||
}: {
|
||||
protection: PolicyProtection;
|
||||
protectionMode: ProtectionModes;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
label: string;
|
||||
}) => {
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const dispatch = useDispatch<(action: AppAction) => void>();
|
||||
const radioButtonId = useMemo(() => htmlIdGenerator()(), []);
|
||||
const selected = policyDetailsConfig && policyDetailsConfig.windows[protection].mode;
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
|
||||
const handleRadioChange = useCallback(() => {
|
||||
if (policyDetailsConfig) {
|
||||
const newPayload = cloneDeep(policyDetailsConfig);
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os][protection].mode = protectionMode;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os][protection as MacPolicyProtection].mode = protectionMode;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os][protection as LinuxPolicyProtection].mode = protectionMode;
|
||||
}
|
||||
if (isPlatinumPlus) {
|
||||
if (os === 'windows') {
|
||||
if (protectionMode === ProtectionModes.prevent) {
|
||||
newPayload[os].popup[protection].enabled = true;
|
||||
} else {
|
||||
newPayload[os].popup[protection].enabled = false;
|
||||
}
|
||||
} else if (os === 'mac') {
|
||||
if (protectionMode === ProtectionModes.prevent) {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled = true;
|
||||
} else {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled = false;
|
||||
}
|
||||
} else if (os === 'linux') {
|
||||
if (protectionMode === ProtectionModes.prevent) {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled = true;
|
||||
} else {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: newPayload },
|
||||
});
|
||||
}
|
||||
}, [dispatch, protectionMode, policyDetailsConfig, isPlatinumPlus, osList, protection]);
|
||||
|
||||
/**
|
||||
* Passing an arbitrary id because EuiRadio
|
||||
* requires an id if label is passed
|
||||
*/
|
||||
|
||||
return (
|
||||
<EuiRadio
|
||||
className="policyDetailsProtectionRadio"
|
||||
label={label}
|
||||
id={radioButtonId}
|
||||
checked={selected === protectionMode}
|
||||
onChange={handleRadioChange}
|
||||
disabled={!showEditableFormFields || selected === ProtectionModes.off}
|
||||
data-test-subj={`${protection}ProtectionMode_${protectionMode}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ProtectionRadio.displayName = 'ProtectionRadio';
|
|
@ -1,134 +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, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSwitch } from '@elastic/eui';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import type { AppAction } from '../../../../../../common/store/actions';
|
||||
import type {
|
||||
ImmutableArray,
|
||||
UIPolicyConfig,
|
||||
AdditionalOnSwitchChangeParams,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection, MacPolicyProtection, LinuxPolicyProtection } from '../../../types';
|
||||
|
||||
export const ProtectionSwitch = React.memo(
|
||||
({
|
||||
protection,
|
||||
protectionLabel,
|
||||
osList,
|
||||
additionalOnSwitchChange,
|
||||
}: {
|
||||
protection: PolicyProtection;
|
||||
protectionLabel?: string;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
additionalOnSwitchChange?: ({
|
||||
value,
|
||||
policyConfigData,
|
||||
protectionOsList,
|
||||
}: AdditionalOnSwitchChangeParams) => UIPolicyConfig;
|
||||
}) => {
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
const dispatch = useDispatch<(action: AppAction) => void>();
|
||||
const selected = policyDetailsConfig && policyDetailsConfig.windows[protection].mode;
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) => {
|
||||
if (policyDetailsConfig) {
|
||||
const newPayload = cloneDeep(policyDetailsConfig);
|
||||
if (event.target.checked === false) {
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os][protection].mode = ProtectionModes.off;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os][protection as MacPolicyProtection].mode = ProtectionModes.off;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os][protection as LinuxPolicyProtection].mode = ProtectionModes.off;
|
||||
}
|
||||
if (isPlatinumPlus) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].enabled = event.target.checked;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os][protection].mode = ProtectionModes.prevent;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os][protection as MacPolicyProtection].mode = ProtectionModes.prevent;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os][protection as LinuxPolicyProtection].mode = ProtectionModes.prevent;
|
||||
}
|
||||
if (isPlatinumPlus) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].enabled = event.target.checked;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (additionalOnSwitchChange) {
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: {
|
||||
policyConfig: additionalOnSwitchChange({
|
||||
value: event.target.checked,
|
||||
policyConfigData: newPayload,
|
||||
protectionOsList: osList,
|
||||
}),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: newPayload },
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch, policyDetailsConfig, isPlatinumPlus, protection, osList, additionalOnSwitchChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.securitySolution.endpoint.policy.details.protectionsEnabled', {
|
||||
defaultMessage: '{protectionLabel} {mode, select, true {enabled} false {disabled}}',
|
||||
values: {
|
||||
protectionLabel,
|
||||
mode: selected !== ProtectionModes.off,
|
||||
},
|
||||
})}
|
||||
checked={selected !== ProtectionModes.off}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={!showEditableFormFields}
|
||||
data-test-subj={`${protection}ProtectionSwitch`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ProtectionSwitch.displayName = 'ProtectionSwitch';
|
|
@ -1,86 +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, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSpacer, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import type {
|
||||
Immutable,
|
||||
ImmutableArray,
|
||||
UIPolicyConfig,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection } from '../../../types';
|
||||
import { ConfigFormHeading } from '../../components/config_form';
|
||||
import { ProtectionRadio } from './protection_radio';
|
||||
|
||||
export const RadioButtons = React.memo(
|
||||
({
|
||||
protection,
|
||||
osList,
|
||||
}: {
|
||||
protection: PolicyProtection;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
}) => {
|
||||
const radios: Immutable<
|
||||
Array<{
|
||||
id: ProtectionModes;
|
||||
label: string;
|
||||
}>
|
||||
> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
id: ProtectionModes.detect,
|
||||
label: i18n.translate('xpack.securitySolution.endpoint.policy.details.detect', {
|
||||
defaultMessage: 'Detect',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: ProtectionModes.prevent,
|
||||
label: i18n.translate('xpack.securitySolution.endpoint.policy.details.prevent', {
|
||||
defaultMessage: 'Prevent',
|
||||
}),
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigFormHeading>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.protectionLevel"
|
||||
defaultMessage="Protection level"
|
||||
/>
|
||||
</ConfigFormHeading>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<ProtectionRadio
|
||||
protection={protection}
|
||||
protectionMode={radios[0].id}
|
||||
osList={osList}
|
||||
key={{ protection } + radios[0].id}
|
||||
label={radios[0].label}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={5}>
|
||||
<ProtectionRadio
|
||||
protection={protection}
|
||||
protectionMode={radios[1].id}
|
||||
osList={osList}
|
||||
key={{ protection } + radios[1].id}
|
||||
label={radios[1].label}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RadioButtons.displayName = 'RadioButtons';
|
|
@ -1,30 +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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { popupVersionsMap } from '../protections/popup_options_to_versions';
|
||||
|
||||
export const SupportedVersionNotice = ({ optionName }: { optionName: string }) => {
|
||||
const version = popupVersionsMap.get(optionName);
|
||||
if (!version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText color="subdued" size="xs" data-test-subj="policySupportedVersions">
|
||||
<i>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.supportedVersion"
|
||||
defaultMessage="Agent version {version}"
|
||||
values={{ version }}
|
||||
/>
|
||||
</i>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -1,210 +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, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiCheckbox,
|
||||
EuiIconTip,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
} from '@elastic/eui';
|
||||
import type { ImmutableArray, UIPolicyConfig } from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection, MacPolicyProtection, LinuxPolicyProtection } from '../../../types';
|
||||
import { ConfigFormHeading } from '../../components/config_form';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import type { AppAction } from '../../../../../../common/store/actions';
|
||||
import { SupportedVersionNotice } from './supported_version';
|
||||
|
||||
export const UserNotification = React.memo(
|
||||
({
|
||||
protection,
|
||||
osList,
|
||||
}: {
|
||||
protection: PolicyProtection;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
}) => {
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const dispatch = useDispatch<(action: AppAction) => void>();
|
||||
const selected = policyDetailsConfig && policyDetailsConfig.windows[protection].mode;
|
||||
const userNotificationSelected =
|
||||
policyDetailsConfig && policyDetailsConfig.windows.popup[protection].enabled;
|
||||
const userNotificationMessage =
|
||||
policyDetailsConfig && policyDetailsConfig.windows.popup[protection].message;
|
||||
|
||||
const handleUserNotificationCheckbox = useCallback(
|
||||
(event) => {
|
||||
if (policyDetailsConfig) {
|
||||
const newPayload = cloneDeep(policyDetailsConfig);
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].enabled = event.target.checked;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: newPayload },
|
||||
});
|
||||
}
|
||||
},
|
||||
[policyDetailsConfig, dispatch, protection, osList]
|
||||
);
|
||||
|
||||
const handleCustomUserNotification = useCallback(
|
||||
(event) => {
|
||||
if (policyDetailsConfig) {
|
||||
const newPayload = cloneDeep(policyDetailsConfig);
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].message = event.target.value;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].message = event.target.value;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].message =
|
||||
event.target.value;
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: newPayload },
|
||||
});
|
||||
}
|
||||
},
|
||||
[policyDetailsConfig, dispatch, protection, osList]
|
||||
);
|
||||
|
||||
const tooltipProtectionText = (protectionType: PolicyProtection) => {
|
||||
if (protectionType === 'memory_protection') {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.memoryProtectionTooltip',
|
||||
{
|
||||
defaultMessage: 'memory threat',
|
||||
}
|
||||
);
|
||||
} else if (protectionType === 'behavior_protection') {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.behaviorProtectionTooltip',
|
||||
{
|
||||
defaultMessage: 'malicious behavior',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return protectionType;
|
||||
}
|
||||
};
|
||||
|
||||
const tooltipBracketText = (protectionType: PolicyProtection) => {
|
||||
if (protectionType === 'memory_protection' || protection === 'behavior_protection') {
|
||||
return i18n.translate('xpack.securitySolution.endpoint.policyDetail.rule', {
|
||||
defaultMessage: 'rule',
|
||||
});
|
||||
} else {
|
||||
return i18n.translate('xpack.securitySolution.endpoint.policyDetail.filename', {
|
||||
defaultMessage: 'filename',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<ConfigFormHeading>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.userNotification"
|
||||
defaultMessage="User notification"
|
||||
/>
|
||||
</ConfigFormHeading>
|
||||
<SupportedVersionNotice optionName={protection} />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCheckbox
|
||||
data-test-subj={`${protection}UserNotificationCheckbox`}
|
||||
id={`${protection}UserNotificationCheckbox}`}
|
||||
onChange={handleUserNotificationCheckbox}
|
||||
checked={userNotificationSelected}
|
||||
disabled={!showEditableFormFields || selected === ProtectionModes.off}
|
||||
label={i18n.translate('xpack.securitySolution.endpoint.policyDetail.notifyUser', {
|
||||
defaultMessage: 'Notify user',
|
||||
})}
|
||||
/>
|
||||
{userNotificationSelected && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification"
|
||||
defaultMessage="Customize notification message"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj={`${protection}TooltipIcon`}>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
data-test-subj={`${protection}Tooltip`}
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.a"
|
||||
defaultMessage="Selecting the user notification option will display a notification to the host user when { protectionName } is prevented or detected."
|
||||
values={{
|
||||
protectionName: tooltipProtectionText(protection),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.c"
|
||||
defaultMessage="
|
||||
The user notification can be customized in the text box below. Bracketed tags can be used to dynamically populate the applicable action (such as prevented or detected) and the { bracketText }."
|
||||
values={{
|
||||
bracketText: tooltipBracketText(protection),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiTextArea
|
||||
placeholder={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetails.userNotification.placeholder',
|
||||
{
|
||||
defaultMessage: 'Input your custom notification message',
|
||||
}
|
||||
)}
|
||||
value={userNotificationMessage}
|
||||
onChange={handleCustomUserNotification}
|
||||
fullWidth={true}
|
||||
disabled={!showEditableFormFields}
|
||||
data-test-subj={`${protection}UserNotificationCustomMessage`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
UserNotification.displayName = 'UserNotification';
|
|
@ -1,93 +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, { memo } from 'react';
|
||||
import {
|
||||
EuiCard,
|
||||
EuiIcon,
|
||||
EuiTextColor,
|
||||
EuiLink,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const LockedPolicyDiv = styled.div`
|
||||
.euiCard__betaBadgeWrapper {
|
||||
.euiCard__betaBadge {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.lockedCardDescription {
|
||||
padding: 0 33.3%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LockedPolicyCard = memo(({ title }: { title: string }) => {
|
||||
return (
|
||||
<LockedPolicyDiv>
|
||||
<EuiCard
|
||||
data-test-subj="lockedPolicyCard"
|
||||
betaBadgeProps={{
|
||||
label: i18n.translate('xpack.securitySolution.endpoint.policy.details.platinum', {
|
||||
defaultMessage: 'Platinum',
|
||||
}),
|
||||
}}
|
||||
isDisabled={true}
|
||||
icon={<EuiIcon size="xl" type="lock" />}
|
||||
title={
|
||||
<h3>
|
||||
<strong>{title}</strong>
|
||||
</h3>
|
||||
}
|
||||
description={false}
|
||||
>
|
||||
<EuiFlexGroup className="lockedCardDescription" direction="column" gutterSize="none">
|
||||
<EuiText>
|
||||
<EuiFlexItem>
|
||||
<h4>
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.upgradeToPlatinum"
|
||||
defaultMessage="Upgrade to Elastic Platinum"
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</h4>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.lockedCardUpgradeMessage"
|
||||
defaultMessage="To turn on this protection, you must upgrade your license to Platinum, start a
|
||||
free 30-day trial, or spin up a {cloudDeploymentLink} on AWS, GCP, or Azure."
|
||||
values={{
|
||||
cloudDeploymentLink: (
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/cloud/"
|
||||
target="_blank"
|
||||
data-test-subj="upgradeNowCloudDeploymentLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.cloudDeploymentLInk"
|
||||
defaultMessage="cloud deployment"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
</EuiCard>
|
||||
</LockedPolicyDiv>
|
||||
);
|
||||
});
|
||||
LockedPolicyCard.displayName = 'LockedPolicyCard';
|
|
@ -1,74 +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 { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import type { Immutable } from '../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import type { BehaviorProtectionOSes } from '../../../types';
|
||||
import { ConfigForm } from '../../components/config_form';
|
||||
import { RadioButtons } from '../components/radio_buttons';
|
||||
import { UserNotification } from '../components/user_notification';
|
||||
import { ProtectionSwitch } from '../components/protection_switch';
|
||||
import { APP_UI_ID } from '../../../../../../../common/constants';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { SecurityPageName } from '../../../../../../app/types';
|
||||
|
||||
/** The Behavior Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
*/
|
||||
export const BehaviorProtection = React.memo(() => {
|
||||
const OSes: Immutable<BehaviorProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
PolicyOperatingSystem.mac,
|
||||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
const protection = 'behavior_protection';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.behavior',
|
||||
{
|
||||
defaultMessage: 'Malicious behavior protections',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<ConfigForm
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.behavior_protection', {
|
||||
defaultMessage: 'Malicious behavior',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
dataTestSubj="behaviorProtectionsForm"
|
||||
rightCorner={
|
||||
<ProtectionSwitch protection={protection} protectionLabel={protectionLabel} osList={OSes} />
|
||||
}
|
||||
>
|
||||
<RadioButtons protection={protection} osList={OSes} />
|
||||
<UserNotification protection={protection} osList={OSes} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
||||
BehaviorProtection.displayName = 'BehaviorProtection';
|
|
@ -1,162 +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, { useCallback, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { APP_UI_ID } from '../../../../../../../common/constants';
|
||||
import { SecurityPageName } from '../../../../../../app/types';
|
||||
import type {
|
||||
Immutable,
|
||||
AdditionalOnSwitchChangeParams,
|
||||
UIPolicyConfig,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import type { MalwareProtectionOSes } from '../../../types';
|
||||
import { ConfigForm } from '../../components/config_form';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import { RadioButtons } from '../components/radio_buttons';
|
||||
import { UserNotification } from '../components/user_notification';
|
||||
import { ProtectionSwitch } from '../components/protection_switch';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import type { AppAction } from '../../../../../../common/store/actions';
|
||||
|
||||
/** The Malware Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
*/
|
||||
export const MalwareProtections = React.memo(() => {
|
||||
const OSes: Immutable<MalwareProtectionOSes[]> = useMemo(
|
||||
() => [PolicyOperatingSystem.windows, PolicyOperatingSystem.mac, PolicyOperatingSystem.linux],
|
||||
[]
|
||||
);
|
||||
const protection = 'malware';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.malware',
|
||||
{
|
||||
defaultMessage: 'Malware protections',
|
||||
}
|
||||
);
|
||||
const blocklistLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.blocklist',
|
||||
{
|
||||
defaultMessage: 'Blocklist enabled',
|
||||
}
|
||||
);
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const dispatch = useDispatch<(action: AppAction) => void>();
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
|
||||
const blocklistUpdate = ({
|
||||
value,
|
||||
policyConfigData,
|
||||
protectionOsList,
|
||||
}: AdditionalOnSwitchChangeParams): UIPolicyConfig => {
|
||||
const newPayload: UIPolicyConfig = cloneDeep(policyConfigData);
|
||||
for (const os of protectionOsList) {
|
||||
newPayload[os][protection].blocklist = value;
|
||||
}
|
||||
|
||||
return newPayload;
|
||||
};
|
||||
|
||||
const handleBlocklistSwitchChange = useCallback(
|
||||
(event) => {
|
||||
if (policyDetailsConfig) {
|
||||
const newPayload = blocklistUpdate({
|
||||
value: event.target.checked,
|
||||
policyConfigData: cloneDeep(policyDetailsConfig),
|
||||
protectionOsList: OSes,
|
||||
});
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: newPayload },
|
||||
});
|
||||
}
|
||||
},
|
||||
[dispatch, OSes, policyDetailsConfig]
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfigForm
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.malware', {
|
||||
defaultMessage: 'Malware',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
dataTestSubj="malwareProtectionsForm"
|
||||
rightCorner={
|
||||
<ProtectionSwitch
|
||||
protection={protection}
|
||||
protectionLabel={protectionLabel}
|
||||
osList={OSes}
|
||||
additionalOnSwitchChange={blocklistUpdate}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RadioButtons protection={protection} osList={OSes} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={blocklistLabel}
|
||||
checked={policyDetailsConfig.windows[protection].blocklist}
|
||||
onChange={handleBlocklistSwitchChange}
|
||||
disabled={
|
||||
!showEditableFormFields || policyDetailsConfig.windows[protection].mode === 'off'
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip"
|
||||
defaultMessage="Enables or disables the blocklist associated with this policy. The blocklist is a collection hashes, paths, or signers which extends the list of processes the endpoint considers malicious. See the blocklist tab for entry details."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isPlatinumPlus && <UserNotification protection={protection} osList={OSes} />}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
||||
MalwareProtections.displayName = 'MalwareProtections';
|
|
@ -1,74 +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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { APP_UI_ID } from '../../../../../../../common/constants';
|
||||
import { SecurityPageName } from '../../../../../../app/types';
|
||||
import type { Immutable } from '../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import type { MemoryProtectionOSes } from '../../../types';
|
||||
import { ConfigForm } from '../../components/config_form';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { RadioButtons } from '../components/radio_buttons';
|
||||
import { UserNotification } from '../components/user_notification';
|
||||
import { ProtectionSwitch } from '../components/protection_switch';
|
||||
|
||||
/** The Memory Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
*/
|
||||
export const MemoryProtection = React.memo(() => {
|
||||
const OSes: Immutable<MemoryProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
PolicyOperatingSystem.mac,
|
||||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
const protection = 'memory_protection';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.memory',
|
||||
{
|
||||
defaultMessage: 'Memory threat protections',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<ConfigForm
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.memory_protection', {
|
||||
defaultMessage: 'Memory threat',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
dataTestSubj="memoryProtectionsForm"
|
||||
rightCorner={
|
||||
<ProtectionSwitch protection={protection} protectionLabel={protectionLabel} osList={OSes} />
|
||||
}
|
||||
>
|
||||
<RadioButtons protection={protection} osList={OSes} />
|
||||
<UserNotification protection={protection} osList={OSes} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
||||
MemoryProtection.displayName = 'MemoryProtection';
|
|
@ -1,15 +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.
|
||||
*/
|
||||
|
||||
const popupVersions: Array<[string, string]> = [
|
||||
['malware', '7.11+'],
|
||||
['ransomware', '7.12+'],
|
||||
['memory_protection', '7.15+'],
|
||||
['behavior_protection', '7.15+'],
|
||||
];
|
||||
|
||||
export const popupVersionsMap: ReadonlyMap<string, string> = new Map<string, string>(popupVersions);
|
|
@ -1,71 +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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { APP_UI_ID } from '../../../../../../../common/constants';
|
||||
import { SecurityPageName } from '../../../../../../app/types';
|
||||
import type { Immutable } from '../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import type { RansomwareProtectionOSes } from '../../../types';
|
||||
import { ConfigForm } from '../../components/config_form';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { RadioButtons } from '../components/radio_buttons';
|
||||
import { UserNotification } from '../components/user_notification';
|
||||
import { ProtectionSwitch } from '../components/protection_switch';
|
||||
|
||||
/** The Ransomware Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
*/
|
||||
export const Ransomware = React.memo(() => {
|
||||
const OSes: Immutable<RansomwareProtectionOSes[]> = [PolicyOperatingSystem.windows];
|
||||
const protection = 'ransomware';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.ransomware',
|
||||
{
|
||||
defaultMessage: 'Ransomware protections',
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfigForm
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.ransomware', {
|
||||
defaultMessage: 'Ransomware',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS]}
|
||||
dataTestSubj="ransomwareProtectionsForm"
|
||||
rightCorner={
|
||||
<ProtectionSwitch protectionLabel={protectionLabel} protection={protection} osList={OSes} />
|
||||
}
|
||||
>
|
||||
<RadioButtons protection={protection} osList={OSes} />
|
||||
<UserNotification protection={protection} osList={OSes} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
||||
Ransomware.displayName = 'RansomwareProtections';
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
|
@ -13,8 +13,6 @@ import {
|
|||
ENDPOINT_EVENT_FILTERS_LIST_ID,
|
||||
ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
import type { PolicyDetailsArtifactsPageLocation, PolicyDetailsState } from '../types';
|
||||
import type { State } from '../../../../common/store';
|
||||
import {
|
||||
|
@ -28,7 +26,7 @@ import {
|
|||
getPolicyHostIsolationExceptionsPath,
|
||||
} from '../../../common/routing';
|
||||
import { getCurrentArtifactsLocation, policyIdFromParams } from '../store/policy_details/selectors';
|
||||
import { APP_UI_ID, POLICIES_PATH } from '../../../../../common/constants';
|
||||
import { POLICIES_PATH } from '../../../../../common/constants';
|
||||
|
||||
/**
|
||||
* Narrows global state down to the PolicyDetailsState before calling the provided Policy Details Selector
|
||||
|
@ -90,27 +88,3 @@ export const useIsPolicySettingsBarVisible = () => {
|
|||
window.location.pathname.includes('/settings')
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if user is granted Write access to Policy Management. This method differs from what
|
||||
* `useUserPrivileges().endpointPrivileges.canWritePolicyManagement` in that it also checks if
|
||||
* user has `canAccessFleet` if form is being displayed outside of Security Solution.
|
||||
* This is to ensure that the Policy Form remains accessible when displayed inside of Fleet
|
||||
* pages if the user does not have privileges to security solution policy management.
|
||||
*/
|
||||
export const useShowEditableFormFields = (): boolean => {
|
||||
const { canWritePolicyManagement, canAccessFleet } = useUserPrivileges().endpointPrivileges;
|
||||
const { getUrlForApp } = useKibana().services.application;
|
||||
|
||||
const securitySolutionUrl = useMemo(() => {
|
||||
return getUrlForApp(APP_UI_ID);
|
||||
}, [getUrlForApp]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (window.location.pathname.startsWith(securitySolutionUrl)) {
|
||||
return canWritePolicyManagement;
|
||||
} else {
|
||||
return canAccessFleet;
|
||||
}
|
||||
}, [canAccessFleet, canWritePolicyManagement, securitySolutionUrl]);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* 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, { memo, useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIconTip,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getEmptyValue } from '../../../../../../common/components/empty_value';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import type { PolicyFormComponentCommonProps } from '../types';
|
||||
import { AdvancedPolicySchema } from '../../../models/advanced_policy_schema';
|
||||
|
||||
function setValue(obj: Record<string, unknown>, value: string, path: string[]) {
|
||||
let newPolicyConfig = obj;
|
||||
|
||||
// First set the value.
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
if (!newPolicyConfig[path[i]]) {
|
||||
newPolicyConfig[path[i]] = {} as Record<string, unknown>;
|
||||
}
|
||||
newPolicyConfig = newPolicyConfig[path[i]] as Record<string, unknown>;
|
||||
}
|
||||
newPolicyConfig[path[path.length - 1]] = value;
|
||||
|
||||
// Then, if the user is deleting the value, we need to ensure we clean up the config.
|
||||
// We delete any sections that are empty, whether that be an empty string, empty object, or undefined.
|
||||
if (value === '' || value === undefined) {
|
||||
newPolicyConfig = obj;
|
||||
for (let k = path.length; k >= 0; k--) {
|
||||
const nextPath = path.slice(0, k);
|
||||
for (let i = 0; i < nextPath.length - 1; i++) {
|
||||
// Traverse and find the next section
|
||||
newPolicyConfig = newPolicyConfig[nextPath[i]] as Record<string, unknown>;
|
||||
}
|
||||
if (
|
||||
newPolicyConfig[nextPath[nextPath.length - 1]] === undefined ||
|
||||
newPolicyConfig[nextPath[nextPath.length - 1]] === '' ||
|
||||
Object.keys(newPolicyConfig[nextPath[nextPath.length - 1]] as object).length === 0
|
||||
) {
|
||||
// If we're looking at the `advanced` field, we leave it undefined as opposed to deleting it.
|
||||
// This is because the UI looks for this field to begin rendering.
|
||||
if (nextPath[nextPath.length - 1] === 'advanced') {
|
||||
newPolicyConfig[nextPath[nextPath.length - 1]] = undefined;
|
||||
// In all other cases, if field is empty, we'll delete it to clean up.
|
||||
} else {
|
||||
delete newPolicyConfig[nextPath[nextPath.length - 1]];
|
||||
}
|
||||
newPolicyConfig = obj;
|
||||
} else {
|
||||
break; // We are looking at a non-empty section, so we can terminate.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(obj: Record<string, unknown>, path: string[]): string {
|
||||
let currentPolicyConfig = obj;
|
||||
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
if (currentPolicyConfig[path[i]]) {
|
||||
currentPolicyConfig = currentPolicyConfig[path[i]] as Record<string, unknown>;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return currentPolicyConfig[path[path.length - 1]] as string;
|
||||
}
|
||||
|
||||
const calloutTitle = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.advanced.calloutTitle',
|
||||
{
|
||||
defaultMessage: 'Proceed with caution!',
|
||||
}
|
||||
);
|
||||
|
||||
const warningMessage = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.advanced.warningMessage',
|
||||
{
|
||||
defaultMessage: `This section contains policy values that support advanced use cases. If not configured
|
||||
properly, these values can cause unpredictable behavior. Please consult documentation
|
||||
carefully or contact support before editing these values.`,
|
||||
}
|
||||
);
|
||||
|
||||
const HIDE = i18n.translate('xpack.securitySolution.endpoint.policy.advanced.hide', {
|
||||
defaultMessage: 'Hide',
|
||||
});
|
||||
const SHOW = i18n.translate('xpack.securitySolution.endpoint.policy.advanced.show', {
|
||||
defaultMessage: 'Show',
|
||||
});
|
||||
|
||||
export type AdvancedSectionProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const AdvancedSection = memo<AdvancedSectionProps>(
|
||||
({ policy, mode, onChange, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const [showAdvancedPolicy, setShowAdvancedPolicy] = useState<boolean>(false);
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
|
||||
const isEditMode = mode === 'edit';
|
||||
|
||||
const handleAdvancedSettingsButtonClick = useCallback(() => {
|
||||
setShowAdvancedPolicy((prevState) => !prevState);
|
||||
}, []);
|
||||
|
||||
const handleAdvancedSettingUpdate = useCallback(
|
||||
(event) => {
|
||||
const updatedPolicy = cloneDeep(policy);
|
||||
|
||||
setValue(
|
||||
updatedPolicy as unknown as Record<string, unknown>,
|
||||
event.target.value,
|
||||
event.target.name.split('.')
|
||||
);
|
||||
|
||||
onChange({ isValid: true, updatedPolicy });
|
||||
},
|
||||
[onChange, policy]
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-test-subj={getTestId()}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj={getTestId('showButton')}
|
||||
onClick={handleAdvancedSettingsButtonClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.advanced.showHideButtonLabel"
|
||||
defaultMessage="{action} advanced settings"
|
||||
values={{ action: showAdvancedPolicy ? HIDE : SHOW }}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{showAdvancedPolicy && (
|
||||
<div>
|
||||
{isEditMode && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={calloutTitle}
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
data-test-subj={getTestId('warning')}
|
||||
>
|
||||
<p>{warningMessage}</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiText size="xs" color="subdued">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.advanced"
|
||||
defaultMessage="Advanced settings"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
|
||||
<EuiPanel data-test-subj={getTestId('settings')} paddingSize="s">
|
||||
{AdvancedPolicySchema.map(
|
||||
(
|
||||
{
|
||||
key,
|
||||
documentation,
|
||||
first_supported_version: firstVersion,
|
||||
last_supported_version: lastVersion,
|
||||
license,
|
||||
},
|
||||
index
|
||||
) => {
|
||||
if (!isPlatinumPlus && license === 'platinum') {
|
||||
return <React.Fragment key={key} />;
|
||||
}
|
||||
|
||||
const configPath = key.split('.');
|
||||
const value = getValue(policy as unknown as Record<string, unknown>, configPath);
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
key={key}
|
||||
fullWidth
|
||||
label={
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem grow={true}>{key}</EuiFlexItem>
|
||||
{documentation && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip content={documentation} position="right" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs">
|
||||
{lastVersion ? `${firstVersion}-${lastVersion}` : `${firstVersion}+`}
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
{isEditMode ? (
|
||||
<EuiFieldText
|
||||
data-test-subj={key}
|
||||
fullWidth
|
||||
name={key}
|
||||
value={value as string}
|
||||
onChange={handleAdvancedSettingUpdate}
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
) : (
|
||||
<EuiText size="xs">{value || getEmptyValue()}</EuiText>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</EuiPanel>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
AdvancedSection.displayName = 'AdvancedSection';
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
||||
const CARD_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type',
|
||||
{
|
||||
defaultMessage: 'Register as antivirus',
|
||||
}
|
||||
);
|
||||
|
||||
const DESCRIPTON = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation',
|
||||
{
|
||||
defaultMessage:
|
||||
'Toggle on to register Elastic as an official Antivirus solution for Windows OS. ' +
|
||||
'This will also disable Windows Defender.',
|
||||
}
|
||||
);
|
||||
|
||||
const REGISTERED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type',
|
||||
{
|
||||
defaultMessage: 'Register as antivirus',
|
||||
}
|
||||
);
|
||||
|
||||
const NOT_REGISTERED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.notRegisteredLabel',
|
||||
{
|
||||
defaultMessage: 'Do not register as antivirus',
|
||||
}
|
||||
);
|
||||
|
||||
type AntivirusRegistrationCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const AntivirusRegistrationCard = memo<AntivirusRegistrationCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isChecked = policy.windows.antivirus_registration.enabled;
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = isChecked ? REGISTERED_LABEL : NOT_REGISTERED_LABEL;
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) => {
|
||||
const updatedPolicy = cloneDeep(policy);
|
||||
updatedPolicy.windows.antivirus_registration.enabled = event.target.checked;
|
||||
|
||||
onChange({ isValid: true, updatedPolicy });
|
||||
},
|
||||
[onChange, policy]
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={CARD_TITLE}
|
||||
supportedOss={[OperatingSystem.WINDOWS]}
|
||||
dataTestSubj={getTestId()}
|
||||
osRestriction={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.av.windowsServerNotSupported',
|
||||
{
|
||||
defaultMessage:
|
||||
'Windows Server operating systems unsupported because Antivirus registration requires Windows Security Center, which is not included in Windows Server operating systems.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{isEditMode && <EuiText size="s">{DESCRIPTON}</EuiText>}
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{isEditMode ? (
|
||||
<EuiSwitch label={label} checked={isChecked} onChange={handleSwitchChange} />
|
||||
) : (
|
||||
<div>{label}</div>
|
||||
)}
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
AntivirusRegistrationCard.displayName = 'AntivirusRegistrationCard';
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiSwitch } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { SettingCard } from '../setting_card';
|
||||
|
||||
const ATTACK_SURFACE_OS_LIST = [OperatingSystem.WINDOWS];
|
||||
|
||||
const LOCKED_CARD_ATTACK_SURFACE_REDUCTION = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.attack_surface_reduction',
|
||||
{
|
||||
defaultMessage: 'Attack Surface Reduction',
|
||||
}
|
||||
);
|
||||
|
||||
const CARD_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.attackSurfaceReduction.type',
|
||||
{
|
||||
defaultMessage: 'Attack surface reduction',
|
||||
}
|
||||
);
|
||||
|
||||
const SWITCH_ENABLED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.credentialHardening.toggleEnabled',
|
||||
{
|
||||
defaultMessage: 'Credential hardening enabled',
|
||||
}
|
||||
);
|
||||
|
||||
const SWITCH_DISABLED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.credentialHardening.toggleDisabled',
|
||||
{
|
||||
defaultMessage: 'Credential hardening disabled',
|
||||
}
|
||||
);
|
||||
|
||||
type AttackSurfaceReductionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const AttackSurfaceReductionCard = memo<AttackSurfaceReductionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isChecked = policy.windows.attack_surface_reduction.credential_hardening.enabled;
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = isChecked ? SWITCH_ENABLED_LABEL : SWITCH_DISABLED_LABEL;
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) => {
|
||||
const updatedPolicy = cloneDeep(policy);
|
||||
|
||||
updatedPolicy.windows.attack_surface_reduction.credential_hardening.enabled =
|
||||
event.target.checked;
|
||||
|
||||
onChange({ isValid: true, updatedPolicy });
|
||||
},
|
||||
[onChange, policy]
|
||||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_ATTACK_SURFACE_REDUCTION} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={CARD_TITLE}
|
||||
supportedOss={ATTACK_SURFACE_OS_LIST}
|
||||
dataTestSubj={getTestId()}
|
||||
>
|
||||
{isEditMode ? (
|
||||
<EuiSwitch
|
||||
label={label}
|
||||
checked={isChecked}
|
||||
onChange={handleSwitchChange}
|
||||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
) : (
|
||||
<>{label}</>
|
||||
)}
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
AttackSurfaceReductionCard.displayName = 'AttackSurfaceReductionCard';
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
||||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { BehaviorProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../../common';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
||||
const LOCKED_CARD_BEHAVIOR_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.behavior',
|
||||
{
|
||||
defaultMessage: 'Malicious Behavior',
|
||||
}
|
||||
);
|
||||
|
||||
const BEHAVIOUR_OS_VALUES: Immutable<BehaviorProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
PolicyOperatingSystem.mac,
|
||||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
|
||||
type BehaviourProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const protection = 'behavior_protection';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.behavior',
|
||||
{
|
||||
defaultMessage: 'Malicious behavior protections',
|
||||
}
|
||||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_BEHAVIOR_TITLE} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.behavior_protection', {
|
||||
defaultMessage: 'Malicious behavior',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
dataTestSubj={getTestId()}
|
||||
rightCorner={
|
||||
<ProtectionSettingCardSwitch
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
protectionLabel={protectionLabel}
|
||||
osList={BEHAVIOUR_OS_VALUES}
|
||||
data-test-subj={getTestId()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DetectPreventProtectionLevel
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={BEHAVIOUR_OS_VALUES}
|
||||
/>
|
||||
|
||||
<NotifyUserOption
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={BEHAVIOUR_OS_VALUES}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
BehaviourProtectionCard.displayName = 'BehaviourProtectionCard';
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* 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 type { ReactElement, ReactNode } from 'react';
|
||||
import React, { memo, useCallback, useContext, useMemo } from 'react';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiBetaBadge,
|
||||
EuiCheckbox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIconTip,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import { cloneDeep, get, set } from 'lodash';
|
||||
import type { EuiCheckboxProps } from '@elastic/eui/src/components/form/checkbox/checkbox';
|
||||
import { getEmptyValue } from '../../../../../../../common/components/empty_value';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { SettingCard, SettingCardHeader } from '../setting_card';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { UIPolicyConfig } from '../../../../../../../../common/endpoint/types';
|
||||
|
||||
const mapOperatingSystemToPolicyOsKey = {
|
||||
[OperatingSystem.WINDOWS]: PolicyOperatingSystem.windows,
|
||||
[OperatingSystem.LINUX]: PolicyOperatingSystem.linux,
|
||||
[OperatingSystem.MAC]: PolicyOperatingSystem.mac,
|
||||
} as const;
|
||||
|
||||
type OperatingSystemToOsMap = typeof mapOperatingSystemToPolicyOsKey;
|
||||
|
||||
export type ProtectionField<T extends OperatingSystem> =
|
||||
keyof UIPolicyConfig[OperatingSystemToOsMap[T]]['events'];
|
||||
|
||||
export type EventFormSelection<T extends OperatingSystem> = { [K in ProtectionField<T>]: boolean };
|
||||
|
||||
export interface EventFormOption<T extends OperatingSystem> {
|
||||
name: string;
|
||||
protectionField: ProtectionField<T>;
|
||||
}
|
||||
|
||||
export interface SupplementalEventFormOption<T extends OperatingSystem> {
|
||||
id?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
name: string;
|
||||
uncheckedName?: string;
|
||||
protectionField: ProtectionField<T>;
|
||||
tooltipText?: string;
|
||||
beta?: boolean;
|
||||
indented?: boolean;
|
||||
isDisabled?(policyConfig: UIPolicyConfig): boolean;
|
||||
}
|
||||
|
||||
export interface EventCollectionCardProps<T extends OperatingSystem = OperatingSystem>
|
||||
extends PolicyFormComponentCommonProps {
|
||||
os: T;
|
||||
options: ReadonlyArray<EventFormOption<T>>;
|
||||
selection: EventFormSelection<T>;
|
||||
supplementalOptions?: ReadonlyArray<SupplementalEventFormOption<T>>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type ANY = any;
|
||||
interface EventCollectionCardComponent {
|
||||
<T extends OperatingSystem>(props: EventCollectionCardProps<T>, context?: ANY): ReactElement<
|
||||
ANY,
|
||||
ANY
|
||||
> | null;
|
||||
displayName?: string | undefined;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const EventCollectionCard = memo(
|
||||
<T extends OperatingSystem>({
|
||||
policy,
|
||||
onChange,
|
||||
mode,
|
||||
os,
|
||||
options,
|
||||
selection,
|
||||
supplementalOptions,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: EventCollectionCardProps<T>) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isEditMode = mode === 'edit';
|
||||
const theme = useContext(ThemeContext);
|
||||
const totalOptions = options.length;
|
||||
const policyOs = mapOperatingSystemToPolicyOsKey[os];
|
||||
|
||||
const selectedCount: number = useMemo(() => {
|
||||
const supplementalSelectionFields: string[] = supplementalOptions
|
||||
? supplementalOptions.map((value) => value.protectionField as string)
|
||||
: [];
|
||||
return Object.entries(selection).filter(([key, value]) =>
|
||||
!supplementalSelectionFields.includes(key) ? value : false
|
||||
).length;
|
||||
}, [selection, supplementalOptions]);
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollection', {
|
||||
defaultMessage: 'Event collection',
|
||||
})}
|
||||
supportedOss={[os]}
|
||||
rightCorner={
|
||||
<EuiText size="s" color="subdued">
|
||||
{i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled',
|
||||
{
|
||||
defaultMessage: '{selected} / {total} event collections enabled',
|
||||
values: {
|
||||
selected: selectedCount,
|
||||
total: totalOptions,
|
||||
},
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
}
|
||||
dataTestSubj={getTestId()}
|
||||
>
|
||||
<SettingCardHeader>
|
||||
{i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents', {
|
||||
defaultMessage: 'Events',
|
||||
})}
|
||||
</SettingCardHeader>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{options.map(({ name, protectionField }) => {
|
||||
const keyPath = `${policyOs}.events.${protectionField}`;
|
||||
|
||||
return (
|
||||
<EventCheckbox
|
||||
label={name}
|
||||
key={keyPath}
|
||||
keyPath={keyPath}
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId(protectionField as string)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{selectedCount === 0 && !isEditMode && <div>{getEmptyValue()}</div>}
|
||||
|
||||
{supplementalOptions &&
|
||||
supplementalOptions.map(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
name,
|
||||
uncheckedName,
|
||||
protectionField,
|
||||
tooltipText,
|
||||
beta,
|
||||
indented,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const keyPath = `${policyOs}.events.${protectionField}`;
|
||||
const isChecked = get(policy, keyPath);
|
||||
|
||||
if (!isEditMode && !isChecked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={String(protectionField)}
|
||||
style={indented ? { paddingLeft: theme.eui.euiSizeL } : {}}
|
||||
>
|
||||
{title && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<SettingCardHeader>{title}</SettingCardHeader>
|
||||
</>
|
||||
)}
|
||||
|
||||
{description && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
{description}
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiFlexGroup direction="row" gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EventCheckbox
|
||||
label={name}
|
||||
unCheckedLabel={uncheckedName}
|
||||
key={keyPath}
|
||||
keyPath={keyPath}
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
disabled={isDisabled ? isDisabled(policy) : false}
|
||||
data-test-subj={getTestId(protectionField as string)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{tooltipText && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip position="right" content={tooltipText} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{beta && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge label="beta" size="s" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
) as EventCollectionCardComponent;
|
||||
EventCollectionCard.displayName = 'EventCollectionCard';
|
||||
|
||||
interface EventCheckboxProps
|
||||
extends PolicyFormComponentCommonProps,
|
||||
Pick<EuiCheckboxProps, 'label' | 'disabled'> {
|
||||
keyPath: string;
|
||||
unCheckedLabel?: ReactNode;
|
||||
}
|
||||
|
||||
const EventCheckbox = memo<EventCheckboxProps>(
|
||||
({
|
||||
policy,
|
||||
onChange,
|
||||
label,
|
||||
unCheckedLabel,
|
||||
mode,
|
||||
keyPath,
|
||||
disabled,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const checkboxId = useGeneratedHtmlId();
|
||||
const isChecked: boolean = get(policy, keyPath);
|
||||
const isEditMode = mode === 'edit';
|
||||
const displayLabel = isChecked ? label : unCheckedLabel ? unCheckedLabel : label;
|
||||
|
||||
const checkboxOnChangeHandler = useCallback(
|
||||
(ev) => {
|
||||
const updatedPolicy = cloneDeep(policy);
|
||||
set(updatedPolicy, keyPath, ev.target.checked);
|
||||
|
||||
onChange({ isValid: true, updatedPolicy });
|
||||
},
|
||||
[keyPath, onChange, policy]
|
||||
);
|
||||
|
||||
return isEditMode ? (
|
||||
<EuiCheckbox
|
||||
key={keyPath}
|
||||
id={checkboxId}
|
||||
label={displayLabel}
|
||||
data-test-subj={dataTestSubj}
|
||||
checked={isChecked}
|
||||
onChange={checkboxOnChangeHandler}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : isChecked ? (
|
||||
<div>{displayLabel}</div>
|
||||
) : null;
|
||||
}
|
||||
);
|
||||
EventCheckbox.displayName = 'EventCheckbox';
|
|
@ -5,16 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import { setIn } from '../../../models/policy_details_config';
|
||||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import type { EventFormOption, SupplementalEventFormOption } from '../../components/events_form';
|
||||
import { EventsForm } from '../../components/events_form';
|
||||
import type { UIPolicyConfig } from '../../../../../../../common/endpoint/types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import type { UIPolicyConfig } from '../../../../../../../../common/endpoint/types';
|
||||
import type { EventFormOption, SupplementalEventFormOption } from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
|
||||
const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.LINUX>> = [
|
||||
{
|
||||
|
@ -45,6 +42,7 @@ const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.LINUX>> = [
|
|||
|
||||
const SUPPLEMENTAL_OPTIONS: ReadonlyArray<SupplementalEventFormOption<OperatingSystem.LINUX>> = [
|
||||
{
|
||||
id: 'sessionDataSection',
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.title',
|
||||
{
|
||||
|
@ -59,11 +57,17 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray<SupplementalEventFormOption<OperatingS
|
|||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data',
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_dataChecked',
|
||||
{
|
||||
defaultMessage: 'Collect session data',
|
||||
}
|
||||
),
|
||||
uncheckedName: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_dataUnChecked',
|
||||
{
|
||||
defaultMessage: 'Do not collect session data',
|
||||
}
|
||||
),
|
||||
protectionField: 'session_data',
|
||||
isDisabled: (config: UIPolicyConfig) => {
|
||||
return !config.linux.events.process;
|
||||
|
@ -71,11 +75,17 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray<SupplementalEventFormOption<OperatingS
|
|||
},
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io',
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_ioChecked',
|
||||
{
|
||||
defaultMessage: 'Capture terminal output',
|
||||
}
|
||||
),
|
||||
uncheckedName: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_ioUnChecked',
|
||||
{
|
||||
defaultMessage: 'Do not capture terminal output',
|
||||
}
|
||||
),
|
||||
protectionField: 'tty_io',
|
||||
tooltipText: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io.tooltip',
|
||||
|
@ -92,32 +102,35 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray<SupplementalEventFormOption<OperatingS
|
|||
},
|
||||
];
|
||||
|
||||
export const LinuxEvents = memo(() => {
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const dispatch = useDispatch();
|
||||
type LinuxEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const LinuxEventCollectionCard = memo<LinuxEventCollectionCardProps>((props) => {
|
||||
const supplementalOptions = useMemo(() => {
|
||||
if (props.mode === 'edit') {
|
||||
return SUPPLEMENTAL_OPTIONS;
|
||||
}
|
||||
|
||||
// View only mode: remove instructions for session data
|
||||
return SUPPLEMENTAL_OPTIONS.map((option) => {
|
||||
if (option.id === 'sessionDataSection') {
|
||||
return {
|
||||
...option,
|
||||
description: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return option;
|
||||
});
|
||||
}, [props.mode]);
|
||||
|
||||
return (
|
||||
<EventsForm<OperatingSystem.LINUX>
|
||||
<EventCollectionCard<OperatingSystem.LINUX>
|
||||
{...props}
|
||||
os={OperatingSystem.LINUX}
|
||||
selection={policyDetailsConfig.linux.events}
|
||||
selection={props.policy.linux.events}
|
||||
supplementalOptions={supplementalOptions}
|
||||
options={OPTIONS}
|
||||
supplementalOptions={SUPPLEMENTAL_OPTIONS}
|
||||
onValueSelection={(value, selected) => {
|
||||
let newConfig = setIn(policyDetailsConfig)('linux')('events')(value)(selected);
|
||||
|
||||
if (value === 'session_data' && !selected) {
|
||||
newConfig = setIn(newConfig)('linux')('events')('tty_io')(false);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: {
|
||||
policyConfig: newConfig,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
LinuxEvents.displayName = 'LinuxEvents';
|
||||
LinuxEventCollectionCard.displayName = 'LinuxEventCollectionCard';
|
|
@ -6,14 +6,11 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import { setIn } from '../../../models/policy_details_config';
|
||||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import type { EventFormOption } from '../../components/events_form';
|
||||
import { EventsForm } from '../../components/events_form';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { EventFormOption } from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
||||
const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.MAC>> = [
|
||||
{
|
||||
|
@ -36,23 +33,16 @@ const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.MAC>> = [
|
|||
},
|
||||
];
|
||||
|
||||
export const MacEvents = memo(() => {
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const dispatch = useDispatch();
|
||||
type MacEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const MacEventCollectionCard = memo<MacEventCollectionCardProps>((props) => {
|
||||
return (
|
||||
<EventsForm<OperatingSystem.MAC>
|
||||
<EventCollectionCard<OperatingSystem.MAC>
|
||||
{...props}
|
||||
os={OperatingSystem.MAC}
|
||||
selection={policyDetailsConfig.mac.events}
|
||||
selection={props.policy.mac.events}
|
||||
options={OPTIONS}
|
||||
onValueSelection={(value, selected) =>
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: { policyConfig: setIn(policyDetailsConfig)('mac')('events')(value)(selected) },
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
MacEvents.displayName = 'MacEvents';
|
||||
MacEventCollectionCard.displayName = 'MacEventCollectionCard';
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { APP_UI_ID } from '../../../../../../../../common';
|
||||
import { SecurityPageName } from '../../../../../../../app/types';
|
||||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { MalwareProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import type { ProtectionSettingCardSwitchProps } from '../protection_setting_card_switch';
|
||||
import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
|
||||
const BLOCKLIST_ENABLED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.blocklistEnabled',
|
||||
{
|
||||
defaultMessage: 'Blocklist enabled',
|
||||
}
|
||||
);
|
||||
|
||||
const BLOCKLIST_DISABLED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.blocklistDisabled',
|
||||
{
|
||||
defaultMessage: 'Blocklist disabled',
|
||||
}
|
||||
);
|
||||
|
||||
// NOTE: it mutates `policyConfigData` passed on input
|
||||
const adjustBlocklistSettingsOnProtectionSwitch: ProtectionSettingCardSwitchProps['additionalOnSwitchChange'] =
|
||||
({ value, policyConfigData, protectionOsList }) => {
|
||||
for (const os of protectionOsList) {
|
||||
policyConfigData[os].malware.blocklist = value;
|
||||
}
|
||||
|
||||
return policyConfigData;
|
||||
};
|
||||
|
||||
const MALWARE_OS_VALUES: Immutable<MalwareProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
PolicyOperatingSystem.mac,
|
||||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
|
||||
export type MalwareProtectionsProps = PolicyFormComponentCommonProps;
|
||||
|
||||
/** The Malware Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
*/
|
||||
export const MalwareProtectionsCard = React.memo<MalwareProtectionsProps>(
|
||||
({ policy, onChange, mode = 'edit', 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const protection = 'malware';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.malware',
|
||||
{
|
||||
defaultMessage: 'Malware protections',
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.malware', {
|
||||
defaultMessage: 'Malware',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
dataTestSubj={getTestId()}
|
||||
rightCorner={
|
||||
<ProtectionSettingCardSwitch
|
||||
protection={protection}
|
||||
protectionLabel={protectionLabel}
|
||||
osList={MALWARE_OS_VALUES}
|
||||
additionalOnSwitchChange={adjustBlocklistSettingsOnProtectionSwitch}
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DetectPreventProtectionLevel
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={MALWARE_OS_VALUES}
|
||||
data-test-subj={getTestId('protectionLevel')}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EnableDisableBlocklist
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId('enableDisableBlocklist')}
|
||||
/>
|
||||
|
||||
<NotifyUserOption
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={MALWARE_OS_VALUES}
|
||||
data-test-subj={getTestId('notifyUser')}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
MalwareProtectionsCard.displayName = 'MalwareProtectionsCard';
|
||||
|
||||
type EnableDisableBlocklistProps = PolicyFormComponentCommonProps;
|
||||
|
||||
const EnableDisableBlocklist = memo<EnableDisableBlocklistProps>(({ policy, onChange, mode }) => {
|
||||
const checked = policy.windows.malware.blocklist;
|
||||
const isDisabled = policy.windows.malware.mode === 'off';
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = checked ? BLOCKLIST_ENABLED_LABEL : BLOCKLIST_DISABLED_LABEL;
|
||||
|
||||
const handleBlocklistSwitchChange = useCallback(
|
||||
(event) => {
|
||||
const value = event.target.checked;
|
||||
const newPayload = cloneDeep(policy);
|
||||
|
||||
adjustBlocklistSettingsOnProtectionSwitch({
|
||||
value,
|
||||
policyConfigData: newPayload,
|
||||
protectionOsList: MALWARE_OS_VALUES,
|
||||
});
|
||||
|
||||
onChange({ isValid: true, updatedPolicy: newPayload });
|
||||
},
|
||||
[onChange, policy]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
{isEditMode ? (
|
||||
<EuiSwitch
|
||||
label={label}
|
||||
checked={checked}
|
||||
onChange={handleBlocklistSwitchChange}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
) : (
|
||||
<>{label}</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip"
|
||||
defaultMessage="Enables or disables the blocklist associated with this policy. The blocklist is a collection hashes, paths, or signers which extends the list of processes the endpoint considers malicious. See the blocklist tab for entry details."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
EnableDisableBlocklist.displayName = 'EnableDisableBlocklist';
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { MemoryProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../../common';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { SettingCard } from '../setting_card';
|
||||
|
||||
const LOCKED_CARD_MEMORY_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.memory',
|
||||
{
|
||||
defaultMessage: 'Memory Threat',
|
||||
}
|
||||
);
|
||||
|
||||
const MEMORY_PROTECTION_OS_VALUES: Immutable<MemoryProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
PolicyOperatingSystem.mac,
|
||||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
|
||||
type MemoryProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const MemoryProtectionCard = memo<MemoryProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const protection = 'memory_protection';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.memory',
|
||||
{
|
||||
defaultMessage: 'Memory threat protections',
|
||||
}
|
||||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_MEMORY_TITLE} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.memory_protection', {
|
||||
defaultMessage: 'Memory threat',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX]}
|
||||
dataTestSubj={getTestId()}
|
||||
rightCorner={
|
||||
<ProtectionSettingCardSwitch
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
protectionLabel={protectionLabel}
|
||||
osList={MEMORY_PROTECTION_OS_VALUES}
|
||||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DetectPreventProtectionLevel
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={MEMORY_PROTECTION_OS_VALUES}
|
||||
data-test-subj={getTestId('protectionLevel')}
|
||||
/>
|
||||
|
||||
<NotifyUserOption
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={MEMORY_PROTECTION_OS_VALUES}
|
||||
data-test-subj={getTestId('notifyUser')}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
MemoryProtectionCard.displayName = 'MemoryProtectionCard';
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { RansomwareProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../../common';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
|
||||
const RANSOMEWARE_OS_VALUES: Immutable<RansomwareProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
];
|
||||
|
||||
const LOCKED_CARD_RAMSOMWARE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.ransomware',
|
||||
{
|
||||
defaultMessage: 'Ransomware',
|
||||
}
|
||||
);
|
||||
|
||||
type RansomwareProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const RansomwareProtectionCard = React.memo<RansomwareProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const protection = 'ransomware';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.ransomware',
|
||||
{
|
||||
defaultMessage: 'Ransomware protections',
|
||||
}
|
||||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_RAMSOMWARE_TITLE} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.ransomware', {
|
||||
defaultMessage: 'Ransomware',
|
||||
})}
|
||||
supportedOss={[OperatingSystem.WINDOWS]}
|
||||
dataTestSubj={getTestId()}
|
||||
rightCorner={
|
||||
<ProtectionSettingCardSwitch
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
protectionLabel={protectionLabel}
|
||||
osList={RANSOMEWARE_OS_VALUES}
|
||||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DetectPreventProtectionLevel
|
||||
protection={protection}
|
||||
osList={RANSOMEWARE_OS_VALUES}
|
||||
onChange={onChange}
|
||||
policy={policy}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId('protectionLevel')}
|
||||
/>
|
||||
|
||||
<NotifyUserOption
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
osList={RANSOMEWARE_OS_VALUES}
|
||||
data-test-subj={getTestId('notifyUser')}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
RansomwareProtectionCard.displayName = 'RansomwareProtectionCard';
|
|
@ -7,13 +7,10 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { policyConfig } from '../../../store/policy_details/selectors';
|
||||
import { setIn } from '../../../models/policy_details_config';
|
||||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import type { EventFormOption } from '../../components/events_form';
|
||||
import { EventsForm } from '../../components/events_form';
|
||||
import type { EventFormOption } from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
||||
const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.WINDOWS>> = [
|
||||
{
|
||||
|
@ -87,25 +84,16 @@ const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.WINDOWS>> = [
|
|||
},
|
||||
];
|
||||
|
||||
export const WindowsEvents = memo(() => {
|
||||
const policyDetailsConfig = usePolicyDetailsSelector(policyConfig);
|
||||
const dispatch = useDispatch();
|
||||
type WindowsEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const WindowsEventCollectionCard = memo<WindowsEventCollectionCardProps>((props) => {
|
||||
return (
|
||||
<EventsForm<OperatingSystem.WINDOWS>
|
||||
<EventCollectionCard<OperatingSystem.WINDOWS>
|
||||
{...props}
|
||||
os={OperatingSystem.WINDOWS}
|
||||
selection={policyDetailsConfig.windows.events}
|
||||
selection={props.policy.windows.events}
|
||||
options={OPTIONS}
|
||||
onValueSelection={(value, selected) =>
|
||||
dispatch({
|
||||
type: 'userChangedPolicyConfig',
|
||||
payload: {
|
||||
policyConfig: setIn(policyDetailsConfig)('windows')('events')(value)(selected),
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
WindowsEvents.displayName = 'WindowsEvents';
|
||||
WindowsEventCollectionCard.displayName = 'WindowsEventCollectionCard';
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* 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, { memo, useCallback, useMemo } from 'react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { EuiFlexItemProps } from '@elastic/eui';
|
||||
import { EuiRadio, EuiSpacer, EuiFlexGroup, EuiFlexItem, useGeneratedHtmlId } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import { SettingCardHeader } from './setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../types';
|
||||
import type {
|
||||
ImmutableArray,
|
||||
UIPolicyConfig,
|
||||
Immutable,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { MacPolicyProtection, LinuxPolicyProtection, PolicyProtection } from '../../../types';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
|
||||
const DETECT_LABEL = i18n.translate('xpack.securitySolution.endpoint.policy.details.detect', {
|
||||
defaultMessage: 'Detect',
|
||||
});
|
||||
|
||||
const PREVENT_LABEL = i18n.translate('xpack.securitySolution.endpoint.policy.details.prevent', {
|
||||
defaultMessage: 'Prevent',
|
||||
});
|
||||
|
||||
export type DetectPreventProtectionLavelProps = PolicyFormComponentCommonProps & {
|
||||
protection: PolicyProtection;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
};
|
||||
|
||||
export const DetectPreventProtectionLevel = memo<DetectPreventProtectionLavelProps>(
|
||||
({ policy, protection, osList, mode, onChange, 'data-test-subj': dataTestSubj }) => {
|
||||
const isEditMode = mode === 'edit';
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const radios: Immutable<
|
||||
Array<{
|
||||
id: ProtectionModes;
|
||||
label: string;
|
||||
flexGrow: EuiFlexItemProps['grow'];
|
||||
}>
|
||||
> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
id: ProtectionModes.detect,
|
||||
label: DETECT_LABEL,
|
||||
flexGrow: 1,
|
||||
},
|
||||
{
|
||||
id: ProtectionModes.prevent,
|
||||
label: PREVENT_LABEL,
|
||||
flexGrow: 5,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
const currentProtectionLevelLabel = useMemo(() => {
|
||||
const radio = radios.find((item) => item.id === policy.windows[protection].mode);
|
||||
|
||||
if (radio) {
|
||||
return radio.label;
|
||||
}
|
||||
|
||||
return PREVENT_LABEL;
|
||||
}, [policy.windows, protection, radios]);
|
||||
|
||||
return (
|
||||
<div data-test-subj={getTestId()}>
|
||||
<SettingCardHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.protectionLevel"
|
||||
defaultMessage="Protection level"
|
||||
/>
|
||||
</SettingCardHeader>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup>
|
||||
{isEditMode ? (
|
||||
radios.map(({ label, id, flexGrow }) => {
|
||||
return (
|
||||
<EuiFlexItem grow={flexGrow} key={id}>
|
||||
<ProtectionRadio
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
protection={protection}
|
||||
protectionMode={id}
|
||||
osList={osList}
|
||||
label={label}
|
||||
data-test-subj={getTestId(`${id}Radio`)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<>{currentProtectionLevelLabel}</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
DetectPreventProtectionLevel.displayName = 'DetectPreventProtectionLevel';
|
||||
|
||||
interface ProtectionRadioProps extends PolicyFormComponentCommonProps {
|
||||
protection: PolicyProtection;
|
||||
protectionMode: ProtectionModes;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const ProtectionRadio = React.memo(
|
||||
({
|
||||
protection,
|
||||
protectionMode,
|
||||
osList,
|
||||
label,
|
||||
onChange,
|
||||
policy,
|
||||
mode,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: ProtectionRadioProps) => {
|
||||
const radioButtonId = useGeneratedHtmlId();
|
||||
const selected = policy.windows[protection].mode;
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const showEditableFormFields = mode === 'edit';
|
||||
|
||||
const handleRadioChange = useCallback(() => {
|
||||
const newPayload = cloneDeep(policy);
|
||||
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os][protection].mode = protectionMode;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os][protection as MacPolicyProtection].mode = protectionMode;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os][protection as LinuxPolicyProtection].mode = protectionMode;
|
||||
}
|
||||
if (isPlatinumPlus) {
|
||||
if (os === 'windows') {
|
||||
if (protectionMode === ProtectionModes.prevent) {
|
||||
newPayload[os].popup[protection].enabled = true;
|
||||
} else {
|
||||
newPayload[os].popup[protection].enabled = false;
|
||||
}
|
||||
} else if (os === 'mac') {
|
||||
if (protectionMode === ProtectionModes.prevent) {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled = true;
|
||||
} else {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled = false;
|
||||
}
|
||||
} else if (os === 'linux') {
|
||||
if (protectionMode === ProtectionModes.prevent) {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled = true;
|
||||
} else {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChange({ isValid: true, updatedPolicy: newPayload });
|
||||
}, [isPlatinumPlus, onChange, osList, policy, protection, protectionMode]);
|
||||
|
||||
return (
|
||||
<EuiRadio
|
||||
label={label}
|
||||
id={radioButtonId}
|
||||
checked={selected === protectionMode}
|
||||
onChange={handleRadioChange}
|
||||
disabled={!showEditableFormFields || selected === ProtectionModes.off}
|
||||
data-test-subj={dataTestSubj}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ProtectionRadio.displayName = 'ProtectionRadio';
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* 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, { useCallback, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
EuiSpacer,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiCheckbox,
|
||||
EuiIconTip,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
} from '@elastic/eui';
|
||||
import { PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION } from '../protection_notice_supported_endpoint_version';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import { getEmptyValue } from '../../../../../../common/components/empty_value';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import { SettingCardHeader } from './setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../types';
|
||||
import type { ImmutableArray, UIPolicyConfig } from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection, MacPolicyProtection, LinuxPolicyProtection } from '../../../types';
|
||||
|
||||
const NOTIFY_USER_CHECKBOX_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.notifyUser',
|
||||
{
|
||||
defaultMessage: 'Notify user',
|
||||
}
|
||||
);
|
||||
|
||||
const DO_NOT_NOTIFY_USER_CHECKBOX_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.doNotNotifyUser',
|
||||
{
|
||||
defaultMessage: "Don't notify user",
|
||||
}
|
||||
);
|
||||
|
||||
const NOTIFICATION_MESSAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.notificationMessage',
|
||||
{
|
||||
defaultMessage: 'Notification message',
|
||||
}
|
||||
);
|
||||
|
||||
const CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification',
|
||||
{
|
||||
defaultMessage: 'Customize notification message',
|
||||
}
|
||||
);
|
||||
|
||||
interface NotifyUserOptionProps extends PolicyFormComponentCommonProps {
|
||||
protection: PolicyProtection;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
}
|
||||
|
||||
export const NotifyUserOption = React.memo(
|
||||
({
|
||||
policy,
|
||||
onChange,
|
||||
mode,
|
||||
protection,
|
||||
osList,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: NotifyUserOptionProps) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const isEditMode = mode === 'edit';
|
||||
const selected = policy.windows[protection].mode;
|
||||
const userNotificationSelected = policy.windows.popup[protection].enabled;
|
||||
const userNotificationMessage = policy.windows.popup[protection].message;
|
||||
const checkboxLabel = userNotificationSelected
|
||||
? NOTIFY_USER_CHECKBOX_LABEL
|
||||
: DO_NOT_NOTIFY_USER_CHECKBOX_LABEL;
|
||||
|
||||
const handleUserNotificationCheckbox = useCallback(
|
||||
(event) => {
|
||||
const newPayload = cloneDeep(policy);
|
||||
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].enabled = event.target.checked;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled = event.target.checked;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
}
|
||||
}
|
||||
|
||||
onChange({ isValid: true, updatedPolicy: newPayload });
|
||||
},
|
||||
[policy, onChange, osList, protection]
|
||||
);
|
||||
|
||||
const handleCustomUserNotification = useCallback(
|
||||
(event) => {
|
||||
const newPayload = cloneDeep(policy);
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].message = event.target.value;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].message = event.target.value;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].message = event.target.value;
|
||||
}
|
||||
}
|
||||
|
||||
onChange({ isValid: true, updatedPolicy: newPayload });
|
||||
},
|
||||
[policy, onChange, osList, protection]
|
||||
);
|
||||
|
||||
const tooltipProtectionText = useCallback((protectionType: PolicyProtection) => {
|
||||
if (protectionType === 'memory_protection') {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.memoryProtectionTooltip',
|
||||
{
|
||||
defaultMessage: 'memory threat',
|
||||
}
|
||||
);
|
||||
} else if (protectionType === 'behavior_protection') {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.behaviorProtectionTooltip',
|
||||
{
|
||||
defaultMessage: 'malicious behavior',
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return protectionType;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const tooltipBracketText = useCallback(
|
||||
(protectionType: PolicyProtection) => {
|
||||
if (protectionType === 'memory_protection' || protection === 'behavior_protection') {
|
||||
return i18n.translate('xpack.securitySolution.endpoint.policyDetail.rule', {
|
||||
defaultMessage: 'rule',
|
||||
});
|
||||
} else {
|
||||
return i18n.translate('xpack.securitySolution.endpoint.policyDetail.filename', {
|
||||
defaultMessage: 'filename',
|
||||
});
|
||||
}
|
||||
},
|
||||
[protection]
|
||||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj={getTestId()}>
|
||||
<EuiSpacer size="m" />
|
||||
<SettingCardHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.userNotification"
|
||||
defaultMessage="User notification"
|
||||
/>
|
||||
</SettingCardHeader>
|
||||
|
||||
<SupportedVersionForProtectionNotice
|
||||
protection={protection}
|
||||
data-test-subj={getTestId('supportedVersion')}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{isEditMode ? (
|
||||
<EuiCheckbox
|
||||
data-test-subj={getTestId('checkbox')}
|
||||
id={`${protection}UserNotificationCheckbox}`}
|
||||
onChange={handleUserNotificationCheckbox}
|
||||
checked={userNotificationSelected}
|
||||
disabled={!isEditMode || selected === ProtectionModes.off}
|
||||
label={checkboxLabel}
|
||||
/>
|
||||
) : (
|
||||
<>{checkboxLabel}</>
|
||||
)}
|
||||
|
||||
{userNotificationSelected &&
|
||||
(isEditMode ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<h4>{CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL}</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
data-test-subj={getTestId('tooltipInfo')}
|
||||
anchorProps={{ 'data-test-subj': getTestId('tooltipIcon') }}
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.a"
|
||||
defaultMessage="Selecting the user notification option will display a notification to the host user when { protectionName } is prevented or detected."
|
||||
values={{
|
||||
protectionName: tooltipProtectionText(protection),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.notifyUserTooltip.c"
|
||||
defaultMessage="
|
||||
The user notification can be customized in the text box below. Bracketed tags can be used to dynamically populate the applicable action (such as prevented or detected) and the { bracketText }."
|
||||
values={{
|
||||
bracketText: tooltipBracketText(protection),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiTextArea
|
||||
placeholder={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetails.userNotification.placeholder',
|
||||
{
|
||||
defaultMessage: 'Input your custom notification message',
|
||||
}
|
||||
)}
|
||||
value={userNotificationMessage}
|
||||
onChange={handleCustomUserNotification}
|
||||
fullWidth={true}
|
||||
disabled={!isEditMode}
|
||||
data-test-subj={getTestId('customMessage')}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<h4>{NOTIFICATION_MESSAGE_LABEL}</h4>
|
||||
</EuiText>
|
||||
<EuiSpacer size="xs" />
|
||||
<>{userNotificationMessage || getEmptyValue()}</>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
NotifyUserOption.displayName = 'NotifyUserOption';
|
||||
|
||||
export const SupportedVersionForProtectionNotice = React.memo(
|
||||
({
|
||||
protection,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: {
|
||||
protection: string;
|
||||
'data-test-subj'?: string;
|
||||
}) => {
|
||||
const version = useMemo(() => {
|
||||
return PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION[
|
||||
protection as keyof typeof PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION
|
||||
];
|
||||
}, [protection]);
|
||||
|
||||
if (!version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText color="subdued" size="xs" data-test-subj={dataTestSubj}>
|
||||
<i>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetails.supportedVersion"
|
||||
defaultMessage="Agent version {version}"
|
||||
values={{ version }}
|
||||
/>
|
||||
</i>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
);
|
||||
SupportedVersionForProtectionNotice.displayName = 'SupportedVersionForProtectionNotice';
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSwitch } from '@elastic/eui';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { PolicyFormComponentCommonProps } from '../types';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import type {
|
||||
ImmutableArray,
|
||||
UIPolicyConfig,
|
||||
PolicyConfig,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection, MacPolicyProtection, LinuxPolicyProtection } from '../../../types';
|
||||
|
||||
export interface ProtectionSettingCardSwitchProps extends PolicyFormComponentCommonProps {
|
||||
protection: PolicyProtection;
|
||||
protectionLabel?: string;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
additionalOnSwitchChange?: ({
|
||||
value,
|
||||
policyConfigData,
|
||||
protectionOsList,
|
||||
}: {
|
||||
value: boolean;
|
||||
policyConfigData: PolicyConfig;
|
||||
protectionOsList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
}) => PolicyConfig;
|
||||
}
|
||||
|
||||
export const ProtectionSettingCardSwitch = React.memo(
|
||||
({
|
||||
protection,
|
||||
protectionLabel,
|
||||
osList,
|
||||
additionalOnSwitchChange,
|
||||
onChange,
|
||||
policy,
|
||||
mode,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: ProtectionSettingCardSwitchProps) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const isEditMode = mode === 'edit';
|
||||
const selected = policy && policy.windows[protection].mode;
|
||||
const switchLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.protectionsEnabled',
|
||||
{
|
||||
defaultMessage: '{protectionLabel} {mode, select, true {enabled} false {disabled}}',
|
||||
values: {
|
||||
protectionLabel,
|
||||
mode: selected !== ProtectionModes.off,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) => {
|
||||
const newPayload = cloneDeep(policy);
|
||||
|
||||
if (event.target.checked === false) {
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os][protection].mode = ProtectionModes.off;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os][protection as MacPolicyProtection].mode = ProtectionModes.off;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os][protection as LinuxPolicyProtection].mode = ProtectionModes.off;
|
||||
}
|
||||
if (isPlatinumPlus) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].enabled = event.target.checked;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const os of osList) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os][protection].mode = ProtectionModes.prevent;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os][protection as MacPolicyProtection].mode = ProtectionModes.prevent;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os][protection as LinuxPolicyProtection].mode = ProtectionModes.prevent;
|
||||
}
|
||||
if (isPlatinumPlus) {
|
||||
if (os === 'windows') {
|
||||
newPayload[os].popup[protection].enabled = event.target.checked;
|
||||
} else if (os === 'mac') {
|
||||
newPayload[os].popup[protection as MacPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
} else if (os === 'linux') {
|
||||
newPayload[os].popup[protection as LinuxPolicyProtection].enabled =
|
||||
event.target.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChange({
|
||||
isValid: true,
|
||||
updatedPolicy: additionalOnSwitchChange
|
||||
? additionalOnSwitchChange({
|
||||
value: event.target.checked,
|
||||
policyConfigData: newPayload,
|
||||
protectionOsList: osList,
|
||||
})
|
||||
: newPayload,
|
||||
});
|
||||
},
|
||||
[policy, onChange, additionalOnSwitchChange, osList, isPlatinumPlus, protection]
|
||||
);
|
||||
|
||||
if (!isEditMode) {
|
||||
return <>{switchLabel}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiSwitch
|
||||
label={switchLabel}
|
||||
checked={selected !== ProtectionModes.off}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={!isEditMode}
|
||||
data-test-subj={dataTestSubj}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ProtectionSettingCardSwitch.displayName = 'ProtectionSettingCardSwitch';
|
|
@ -23,6 +23,7 @@ import {
|
|||
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import type { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import { OS_TITLES } from '../../../../../common/translations';
|
||||
|
||||
const TITLES = {
|
||||
|
@ -34,7 +35,7 @@ const TITLES = {
|
|||
}),
|
||||
};
|
||||
|
||||
interface ConfigFormProps {
|
||||
interface SettingCardProps {
|
||||
/**
|
||||
* A subtitle for this component.
|
||||
**/
|
||||
|
@ -49,19 +50,22 @@ interface ConfigFormProps {
|
|||
rightCorner?: ReactNode;
|
||||
}
|
||||
|
||||
export const ConfigFormHeading: FC = memo(({ children }) => (
|
||||
<EuiTitle size="xxs">
|
||||
<h5>{children}</h5>
|
||||
</EuiTitle>
|
||||
));
|
||||
export const SettingCardHeader = memo<{ children: React.ReactNode; 'data-test-subj'?: string }>(
|
||||
({ children, 'data-test-subj': dataTestSubj }) => (
|
||||
<EuiTitle size="xxs" data-test-subj={dataTestSubj}>
|
||||
<h5>{children}</h5>
|
||||
</EuiTitle>
|
||||
)
|
||||
);
|
||||
SettingCardHeader.displayName = 'SettingCardHeader';
|
||||
|
||||
ConfigFormHeading.displayName = 'ConfigFormHeading';
|
||||
|
||||
export const ConfigForm: FC<ConfigFormProps> = memo(
|
||||
export const SettingCard: FC<SettingCardProps> = memo(
|
||||
({ type, supportedOss, osRestriction, dataTestSubj, rightCorner, children }) => {
|
||||
const paddingSize = useContext(ThemeContext).eui.euiPanelPaddingModifiers.paddingMedium;
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<EuiPanel data-test-subj={dataTestSubj} hasBorder={true} hasShadow={false} paddingSize="none">
|
||||
<EuiPanel data-test-subj={getTestId()} hasBorder={true} hasShadow={false} paddingSize="none">
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
|
@ -69,14 +73,16 @@ export const ConfigForm: FC<ConfigFormProps> = memo(
|
|||
style={{ padding: `${paddingSize} ${paddingSize} 0 ${paddingSize}` }}
|
||||
>
|
||||
<EuiFlexItem grow={1}>
|
||||
<ConfigFormHeading>{TITLES.type}</ConfigFormHeading>
|
||||
<SettingCardHeader data-test-subj={getTestId('title')}>{TITLES.type}</SettingCardHeader>
|
||||
<EuiText size="s">{type}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<ConfigFormHeading>{TITLES.os}</ConfigFormHeading>
|
||||
<SettingCardHeader data-test-subj={getTestId('osTitle')}>{TITLES.os}</SettingCardHeader>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{supportedOss.map((os) => OS_TITLES[os]).join(', ')} </EuiText>
|
||||
<EuiText size="s" data-test-subj={getTestId('osValues')}>
|
||||
{supportedOss.map((os) => OS_TITLES[os]).join(', ')}{' '}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{osRestriction && (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -117,4 +123,4 @@ export const ConfigForm: FC<ConfigFormProps> = memo(
|
|||
}
|
||||
);
|
||||
|
||||
ConfigForm.displayName = 'ConfigForm';
|
||||
SettingCard.displayName = 'SettingCard';
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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, { memo } from 'react';
|
||||
import {
|
||||
EuiCard,
|
||||
EuiIcon,
|
||||
EuiTextColor,
|
||||
EuiLink,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
|
||||
const LockedPolicyDiv = styled.div`
|
||||
.euiCard__betaBadgeWrapper {
|
||||
.euiCard__betaBadge {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.lockedCardDescription {
|
||||
padding: 0 33.3%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingLockedCard = memo(
|
||||
({ title, 'data-test-subj': dataTestSubj }: { title: string; 'data-test-subj'?: string }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<LockedPolicyDiv>
|
||||
<EuiCard
|
||||
data-test-subj={getTestId()}
|
||||
betaBadgeProps={{
|
||||
label: i18n.translate('xpack.securitySolution.endpoint.policy.details.platinum', {
|
||||
defaultMessage: 'Platinum',
|
||||
}),
|
||||
}}
|
||||
isDisabled={true}
|
||||
icon={<EuiIcon size="xl" type="lock" />}
|
||||
title={
|
||||
<h3>
|
||||
<strong>{title}</strong>
|
||||
</h3>
|
||||
}
|
||||
description={false}
|
||||
>
|
||||
<EuiFlexGroup className="lockedCardDescription" direction="column" gutterSize="none">
|
||||
<EuiText>
|
||||
<EuiFlexItem>
|
||||
<h4>
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.upgradeToPlatinum"
|
||||
defaultMessage="Upgrade to Elastic Platinum"
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</h4>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.lockedCardUpgradeMessage"
|
||||
defaultMessage="To turn on this protection, you must upgrade your license to Platinum, start a
|
||||
free 30-day trial, or spin up a {cloudDeploymentLink} on AWS, GCP, or Azure."
|
||||
values={{
|
||||
cloudDeploymentLink: (
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/cloud/"
|
||||
target="_blank"
|
||||
data-test-subj={getTestId('cloudLink')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.cloudDeploymentLInk"
|
||||
defaultMessage="cloud deployment"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
</EuiCard>
|
||||
</LockedPolicyDiv>
|
||||
);
|
||||
}
|
||||
);
|
||||
SettingLockedCard.displayName = 'SettingLockedCard';
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { PolicyFormLayout } from './policy_form_layout';
|
||||
export { PolicySettingsForm } from './policy_settings_form';
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
interface TestSubjGenerator {
|
||||
(suffix?: string): string;
|
||||
withPrefix: (prefix: string) => TestSubjGenerator;
|
||||
}
|
||||
|
||||
export const createTestSubjGenerator = (testSubjPrefix: string): TestSubjGenerator => {
|
||||
const testSubjGenerator: TestSubjGenerator = (suffix) => {
|
||||
if (suffix) {
|
||||
return `${testSubjPrefix}-${suffix}`;
|
||||
}
|
||||
return testSubjPrefix;
|
||||
};
|
||||
|
||||
testSubjGenerator.withPrefix = (prefix: string): TestSubjGenerator => {
|
||||
return createTestSubjGenerator(testSubjGenerator(prefix));
|
||||
};
|
||||
|
||||
return testSubjGenerator;
|
||||
};
|
||||
|
||||
export const getPolicySettingsFormTestSubjects = (
|
||||
formTopLevelTestSubj: string = 'endpointPolicyForm'
|
||||
) => {
|
||||
const genTestSubj = createTestSubjGenerator(formTopLevelTestSubj);
|
||||
const malwareTestSubj = genTestSubj.withPrefix('malware');
|
||||
const ransomwareTestSubj = genTestSubj.withPrefix('ransomware');
|
||||
const memoryTestSubj = genTestSubj.withPrefix('memory');
|
||||
const behaviourTestSubj = genTestSubj.withPrefix('behaviour');
|
||||
const advancedSectionTestSubj = genTestSubj.withPrefix('advancedSection');
|
||||
const windowsEventsTestSubj = genTestSubj.withPrefix('windowsEvents');
|
||||
const macEventsTestSubj = genTestSubj.withPrefix('macEvents');
|
||||
const linuxEventsTestSubj = genTestSubj.withPrefix('linuxEvents');
|
||||
|
||||
const testSubj = {
|
||||
form: genTestSubj(),
|
||||
|
||||
malware: {
|
||||
card: malwareTestSubj(),
|
||||
enableDisableSwitch: malwareTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: malwareTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: malwareTestSubj('protectionLevel-detectRadio'),
|
||||
notifyUserCheckbox: malwareTestSubj('notifyUser-checkbox'),
|
||||
notifySupportedVersion: malwareTestSubj('notifyUser-supportedVersion'),
|
||||
notifyCustomMessage: malwareTestSubj('notifyUser-customMessage'),
|
||||
notifyCustomMessageTooltipIcon: malwareTestSubj('notifyUser-tooltipIcon'),
|
||||
notifyCustomMessageTooltipInfo: malwareTestSubj('notifyUser-tooltipInfo'),
|
||||
osValuesContainer: malwareTestSubj('osValues'),
|
||||
},
|
||||
ransomware: {
|
||||
card: ransomwareTestSubj(),
|
||||
enableDisableSwitch: ransomwareTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: ransomwareTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: ransomwareTestSubj('protectionLevel-detectRadio'),
|
||||
notifyUserCheckbox: ransomwareTestSubj('notifyUser-checkbox'),
|
||||
notifySupportedVersion: ransomwareTestSubj('notifyUser-supportedVersion'),
|
||||
notifyCustomMessage: ransomwareTestSubj('notifyUser-customMessage'),
|
||||
notifyCustomMessageTooltipIcon: ransomwareTestSubj('notifyUser-tooltipIcon'),
|
||||
notifyCustomMessageTooltipInfo: ransomwareTestSubj('notifyUser-tooltipInfo'),
|
||||
osValuesContainer: ransomwareTestSubj('osValues'),
|
||||
},
|
||||
memory: {
|
||||
card: memoryTestSubj(),
|
||||
enableDisableSwitch: memoryTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: memoryTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: memoryTestSubj('protectionLevel-detectRadio'),
|
||||
notifyUserCheckbox: memoryTestSubj('notifyUser-checkbox'),
|
||||
osValuesContainer: memoryTestSubj('osValues'),
|
||||
},
|
||||
behaviour: {
|
||||
card: behaviourTestSubj(),
|
||||
enableDisableSwitch: behaviourTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: behaviourTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: behaviourTestSubj('protectionLevel-detectRadio'),
|
||||
notifyUserCheckbox: behaviourTestSubj('notifyUser-checkbox'),
|
||||
osValuesContainer: behaviourTestSubj('osValues'),
|
||||
},
|
||||
attachSurface: {
|
||||
card: genTestSubj('attackSurface'),
|
||||
enableDisableSwitch: genTestSubj('attachSurface-enableDisableSwitch'),
|
||||
osValuesContainer: genTestSubj('attackSurface-osValues'),
|
||||
},
|
||||
|
||||
windowsEvents: {
|
||||
card: windowsEventsTestSubj(),
|
||||
dnsCheckbox: windowsEventsTestSubj('dns'),
|
||||
processCheckbox: windowsEventsTestSubj('process'),
|
||||
fileCheckbox: windowsEventsTestSubj('file'),
|
||||
},
|
||||
macEvents: {
|
||||
card: macEventsTestSubj(),
|
||||
fileCheckbox: macEventsTestSubj('file'),
|
||||
},
|
||||
linuxEvents: {
|
||||
card: linuxEventsTestSubj(),
|
||||
fileCheckbox: linuxEventsTestSubj('file'),
|
||||
},
|
||||
antivirusRegistration: {
|
||||
card: genTestSubj('antivirusRegistration'),
|
||||
},
|
||||
advancedSection: {
|
||||
container: advancedSectionTestSubj(''),
|
||||
showHideButton: advancedSectionTestSubj('showButton'),
|
||||
settingsContainer: advancedSectionTestSubj('settings'),
|
||||
warningCallout: advancedSectionTestSubj('warning'),
|
||||
},
|
||||
};
|
||||
|
||||
return testSubj;
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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, { memo } from 'react';
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AntivirusRegistrationCard } from './components/cards/antivirus_registration_card';
|
||||
import { LinuxEventCollectionCard } from './components/cards/linux_event_collection_card';
|
||||
import { MacEventCollectionCard } from './components/cards/mac_event_collection_card';
|
||||
import { WindowsEventCollectionCard } from './components/cards/windows_event_collection_card';
|
||||
import { AttackSurfaceReductionCard } from './components/cards/attack_surface_reduction_card';
|
||||
import { BehaviourProtectionCard } from './components/cards/behaviour_protection_card';
|
||||
import { MemoryProtectionCard } from './components/cards/memory_protection_card';
|
||||
import { RansomwareProtectionCard } from './components/cards/ransomware_protection_card';
|
||||
import { MalwareProtectionsCard } from './components/cards/malware_protections_card';
|
||||
import type { PolicyFormComponentCommonProps } from './types';
|
||||
import { AdvancedSection } from './components/advanced_section';
|
||||
import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator';
|
||||
|
||||
const PROTECTIONS_SECTION_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.protections',
|
||||
{ defaultMessage: 'Protections' }
|
||||
);
|
||||
|
||||
const SETTINGS_SECTION_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.settings',
|
||||
{ defaultMessage: 'Settings' }
|
||||
);
|
||||
|
||||
export type PolicySettingsFormProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const PolicySettingsForm = memo<PolicySettingsFormProps>((props) => {
|
||||
const getTestId = useTestIdGenerator(props['data-test-subj']);
|
||||
|
||||
return (
|
||||
<div data-test-subj={getTestId()}>
|
||||
<FormSectionTitle>{PROTECTIONS_SECTION_TITLE}</FormSectionTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<MalwareProtectionsCard {...props} data-test-subj={getTestId('malware')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<RansomwareProtectionCard {...props} data-test-subj={getTestId('ransomware')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<MemoryProtectionCard {...props} data-test-subj={getTestId('memory')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<BehaviourProtectionCard {...props} data-test-subj={getTestId('behaviour')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<AttackSurfaceReductionCard {...props} data-test-subj={getTestId('attackSurface')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<FormSectionTitle>{SETTINGS_SECTION_TITLE}</FormSectionTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<WindowsEventCollectionCard {...props} data-test-subj={getTestId('windowsEvents')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<MacEventCollectionCard {...props} data-test-subj={getTestId('macEvents')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<LinuxEventCollectionCard {...props} data-test-subj={getTestId('linuxEvents')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<AntivirusRegistrationCard {...props} data-test-subj={getTestId('antivirusRegistration')} />
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<AdvancedSection {...props} data-test-subj={getTestId('advancedSection')} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
PolicySettingsForm.displayName = 'PolicySettingsForm';
|
||||
|
||||
const FormSectionTitle = memo(({ children }) => {
|
||||
return (
|
||||
<EuiText size="xs" color="subdued">
|
||||
<h4>{children}</h4>
|
||||
</EuiText>
|
||||
);
|
||||
});
|
||||
FormSectionTitle.displayName = 'FormSectionTitle';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION = Object.freeze({
|
||||
malware: '7.11+',
|
||||
ransomware: '7.12+',
|
||||
memory_protection: '7.15+',
|
||||
behavior_protection: '7.15+',
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 type { PolicyConfig } from '../../../../../../common/endpoint/types';
|
||||
|
||||
export interface PolicyFormComponentCommonProps {
|
||||
policy: PolicyConfig;
|
||||
onChange: (options: { isValid: boolean; updatedPolicy: PolicyConfig }) => void;
|
||||
mode: 'edit' | 'view';
|
||||
'data-test-subj'?: string;
|
||||
}
|
|
@ -5,6 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { WindowsEvents } from './windows';
|
||||
export { MacEvents } from './mac';
|
||||
export { LinuxEvents } from './linux';
|
||||
export { PolicySettingsLayout } from './policy_settings_layout';
|
|
@ -5,64 +5,58 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiLoadingSpinner,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import type { ApplicationStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import type { ApplicationStart } from '@kbn/core-application-browser';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { useShowEditableFormFields, usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import {
|
||||
policyDetails,
|
||||
agentStatusSummary,
|
||||
updateStatus,
|
||||
isLoading,
|
||||
} from '../../../store/policy_details/selectors';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { useFetchAgentByAgentPolicySummary } from '../../../../hooks/policy/use_fetch_endpoint_policy_agent_summary';
|
||||
import { useUpdateEndpointPolicy } from '../../../../hooks/policy/use_update_endpoint_policy';
|
||||
import type { PolicySettingsFormProps } from '../policy_settings_form/policy_settings_form';
|
||||
import { PolicySettingsForm } from '../policy_settings_form';
|
||||
import type {
|
||||
MaybeImmutable,
|
||||
PolicyConfig,
|
||||
PolicyData,
|
||||
PolicyDetailsRouteState,
|
||||
} from '../../../../../../common/endpoint/types';
|
||||
import { useKibana, useToasts } from '../../../../../common/lib/kibana';
|
||||
import { APP_UI_ID } from '../../../../../../common';
|
||||
import { getPoliciesPath } from '../../../../common/routing';
|
||||
import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
|
||||
import { ConfirmUpdate } from './components/policy_form_confirm_update';
|
||||
|
||||
import { useToasts, useKibana } from '../../../../../../common/lib/kibana';
|
||||
import type { AppAction } from '../../../../../../common/store/actions';
|
||||
import { getPoliciesPath } from '../../../../../common/routing';
|
||||
import { useNavigateToAppEventHandler } from '../../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
|
||||
import { APP_UI_ID } from '../../../../../../../common/constants';
|
||||
import type { PolicyDetailsRouteState } from '../../../../../../../common/endpoint/types';
|
||||
import { SecuritySolutionPageWrapper } from '../../../../../../common/components/page_wrapper';
|
||||
import { PolicyDetailsForm } from '../../policy_details_form';
|
||||
import { ConfirmUpdate } from './policy_form_confirm_update';
|
||||
export interface PolicySettingsLayoutProps {
|
||||
policy: MaybeImmutable<PolicyData>;
|
||||
}
|
||||
|
||||
export const PolicyFormLayout = React.memo(() => {
|
||||
const dispatch = useDispatch<(action: AppAction) => void>();
|
||||
export const PolicySettingsLayout = memo<PolicySettingsLayoutProps>(({ policy: _policy }) => {
|
||||
const policy = _policy as PolicyData;
|
||||
const {
|
||||
services: {
|
||||
theme,
|
||||
application: { navigateToApp },
|
||||
},
|
||||
} = useKibana();
|
||||
const toasts = useToasts();
|
||||
const { state: locationRouteState } = useLocation<PolicyDetailsRouteState>();
|
||||
const showEditableFormFields = useShowEditableFormFields();
|
||||
const { canWritePolicyManagement } = useUserPrivileges().endpointPrivileges;
|
||||
const { isLoading: isUpdating, mutateAsync: sendPolicyUpdate } = useUpdateEndpointPolicy();
|
||||
const { data: agentSummaryData } = useFetchAgentByAgentPolicySummary(policy.policy_id);
|
||||
|
||||
// Store values
|
||||
const policyItem = usePolicyDetailsSelector(policyDetails);
|
||||
const policyAgentStatusSummary = usePolicyDetailsSelector(agentStatusSummary);
|
||||
const policyUpdateStatus = usePolicyDetailsSelector(updateStatus);
|
||||
const isPolicyLoading = usePolicyDetailsSelector(isLoading);
|
||||
|
||||
// Local state
|
||||
const [policySettings, setPolicySettings] = useState<PolicyConfig>(
|
||||
cloneDeep(policy.inputs[0].config.policy.value)
|
||||
);
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false);
|
||||
const [routeState, setRouteState] = useState<PolicyDetailsRouteState>();
|
||||
const policyName = policyItem?.name ?? '';
|
||||
|
||||
const isEditMode = canWritePolicyManagement;
|
||||
const policyName = policy?.name ?? '';
|
||||
const routingOnCancelNavigateTo = routeState?.onCancelNavigateTo;
|
||||
|
||||
const navigateToAppArguments = useMemo((): Parameters<ApplicationStart['navigateToApp']> => {
|
||||
if (routingOnCancelNavigateTo) {
|
||||
return routingOnCancelNavigateTo;
|
||||
|
@ -76,43 +70,9 @@ export const PolicyFormLayout = React.memo(() => {
|
|||
];
|
||||
}, [routingOnCancelNavigateTo]);
|
||||
|
||||
// Handle showing update statuses
|
||||
useEffect(() => {
|
||||
if (policyUpdateStatus) {
|
||||
if (policyUpdateStatus.success) {
|
||||
toasts.addSuccess({
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.updateSuccessTitle',
|
||||
{
|
||||
defaultMessage: 'Success!',
|
||||
}
|
||||
),
|
||||
text: toMountPoint(
|
||||
<span data-test-subj="policyDetailsSuccessMessage">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.updateSuccessMessage"
|
||||
defaultMessage="Integration {name} has been updated."
|
||||
values={{ name: policyName }}
|
||||
/>
|
||||
</span>,
|
||||
{ theme$: theme.theme$ }
|
||||
),
|
||||
});
|
||||
|
||||
if (routeState && routeState.onSaveNavigateTo) {
|
||||
navigateToApp(...routeState.onSaveNavigateTo);
|
||||
}
|
||||
} else {
|
||||
toasts.addDanger({
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.policy.details.updateErrorTitle', {
|
||||
defaultMessage: 'Failed!',
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
text: policyUpdateStatus.error!.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [navigateToApp, toasts, policyName, policyUpdateStatus, routeState, theme.theme$]);
|
||||
const handleSettingsOnChange: PolicySettingsFormProps['onChange'] = useCallback((updates) => {
|
||||
setPolicySettings(updates.updatedPolicy);
|
||||
}, []);
|
||||
|
||||
const handleCancelOnClick = useNavigateToAppEventHandler(...navigateToAppArguments);
|
||||
|
||||
|
@ -120,45 +80,84 @@ export const PolicyFormLayout = React.memo(() => {
|
|||
setShowConfirm(true);
|
||||
}, []);
|
||||
|
||||
const handleSaveConfirmation = useCallback(() => {
|
||||
dispatch({
|
||||
type: 'userClickedPolicyDetailsSaveButton',
|
||||
});
|
||||
setShowConfirm(false);
|
||||
}, [dispatch]);
|
||||
|
||||
const handleSaveCancel = useCallback(() => {
|
||||
setShowConfirm(false);
|
||||
}, []);
|
||||
|
||||
const handleSaveConfirmation = useCallback(() => {
|
||||
const update = cloneDeep(policy);
|
||||
|
||||
update.inputs[0].config.policy.value = policySettings;
|
||||
sendPolicyUpdate({ policy: update })
|
||||
.then(() => {
|
||||
toasts.addSuccess({
|
||||
'data-test-subj': 'policyDetailsSuccessMessage',
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.updateSuccessTitle',
|
||||
{
|
||||
defaultMessage: 'Success!',
|
||||
}
|
||||
),
|
||||
text: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.updateSuccessMessage',
|
||||
{
|
||||
defaultMessage: 'Integration {name} has been updated.',
|
||||
values: { name: policyName },
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
if (routeState && routeState.onSaveNavigateTo) {
|
||||
navigateToApp(...routeState.onSaveNavigateTo);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toasts.addDanger({
|
||||
'data-test-subj': 'policyDetailsFailureMessage',
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.policy.details.updateErrorTitle', {
|
||||
defaultMessage: 'Failed!',
|
||||
}),
|
||||
text: err.message,
|
||||
});
|
||||
});
|
||||
|
||||
handleSaveCancel();
|
||||
}, [
|
||||
handleSaveCancel,
|
||||
navigateToApp,
|
||||
policy,
|
||||
policyName,
|
||||
policySettings,
|
||||
routeState,
|
||||
sendPolicyUpdate,
|
||||
toasts,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!routeState && locationRouteState) {
|
||||
setRouteState(locationRouteState);
|
||||
}
|
||||
}, [locationRouteState, routeState]);
|
||||
|
||||
// Before proceeding - check if we have a policy data.
|
||||
// If not, and we are still loading, show spinner.
|
||||
// Else, if we have an error, then show error on the page.
|
||||
if (!policyItem) {
|
||||
return (
|
||||
<SecuritySolutionPageWrapper noTimeline>
|
||||
{isPolicyLoading ? <EuiLoadingSpinner size="xl" /> : null}
|
||||
</SecuritySolutionPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showConfirm && (
|
||||
<ConfirmUpdate
|
||||
endpointCount={policyAgentStatusSummary?.total ?? 0}
|
||||
endpointCount={agentSummaryData ? agentSummaryData.all : 0}
|
||||
onCancel={handleSaveCancel}
|
||||
onConfirm={handleSaveConfirmation}
|
||||
/>
|
||||
)}
|
||||
<PolicyDetailsForm />
|
||||
|
||||
<PolicySettingsForm
|
||||
policy={policySettings}
|
||||
onChange={handleSettingsOnChange}
|
||||
mode={isEditMode ? 'edit' : 'view'}
|
||||
data-test-subj="endpointPolicyForm"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="xxl" />
|
||||
|
||||
<KibanaPageTemplate.BottomBar paddingSize="s">
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -173,14 +172,14 @@ export const PolicyFormLayout = React.memo(() => {
|
|||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{showEditableFormFields && (
|
||||
{isEditMode && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill={true}
|
||||
iconType="save"
|
||||
data-test-subj="policyDetailsSaveButton"
|
||||
onClick={handleSaveOnClick}
|
||||
isLoading={isPolicyLoading}
|
||||
isLoading={isUpdating}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.save"
|
||||
|
@ -194,5 +193,4 @@ export const PolicyFormLayout = React.memo(() => {
|
|||
</>
|
||||
);
|
||||
});
|
||||
|
||||
PolicyFormLayout.displayName = 'PolicyFormLayout';
|
||||
PolicySettingsLayout.displayName = 'PolicySettingsLayout';
|
|
@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { PolicySettingsLayout } from '../policy_settings_layout';
|
||||
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import {
|
||||
getPolicyDetailPath,
|
||||
|
@ -36,7 +37,6 @@ import {
|
|||
policyIdFromParams,
|
||||
} from '../../store/policy_details/selectors';
|
||||
import { PolicyArtifactsLayout } from '../artifacts/layout/policy_artifacts_layout';
|
||||
import { PolicyFormLayout } from '../policy_forms/components';
|
||||
import { usePolicyDetailsSelector } from '../policy_hooks';
|
||||
import { POLICY_ARTIFACT_EVENT_FILTERS_LABELS } from './event_filters_translations';
|
||||
import { POLICY_ARTIFACT_TRUSTED_APPS_LABELS } from './trusted_apps_translations';
|
||||
|
@ -77,7 +77,11 @@ export const PolicyTabs = React.memo(() => {
|
|||
const isInHostIsolationExceptionsTab = usePolicyDetailsSelector(isOnHostIsolationExceptionsView);
|
||||
const isInBlocklistsTab = usePolicyDetailsSelector(isOnBlocklistsView);
|
||||
const policyId = usePolicyDetailsSelector(policyIdFromParams);
|
||||
const policyItem = usePolicyDetailsSelector(policyDetails);
|
||||
|
||||
// By the time the tabs load, we know that we already have a `policyItem` since a conditional
|
||||
// check is done at the `PageDetails` component level. So asserting to non-null/undefined here.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const policyItem = usePolicyDetailsSelector(policyDetails)!;
|
||||
const {
|
||||
canReadTrustedApplications,
|
||||
canWriteTrustedApplications,
|
||||
|
@ -195,7 +199,8 @@ export const PolicyTabs = React.memo(() => {
|
|||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<PolicyFormLayout />
|
||||
|
||||
<PolicySettingsLayout policy={policyItem} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ import type { GetPolicyListResponse } from '../../pages/policy/types';
|
|||
import { sendGetEndpointSpecificPackagePolicies } from './policies';
|
||||
import type { ServerApiError } from '../../../common/types';
|
||||
|
||||
// FIXME:PT move to `hooks` folder
|
||||
export function useGetEndpointSpecificPolicies(
|
||||
{
|
||||
onError,
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
import type { HttpFetchOptions, HttpStart } from '@kbn/core/public';
|
||||
import type {
|
||||
GetAgentStatusResponse,
|
||||
GetAgentPoliciesRequest,
|
||||
GetAgentPoliciesResponse,
|
||||
GetPackagePoliciesResponse,
|
||||
GetInfoResponse,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
|
@ -59,38 +57,6 @@ export const sendBulkGetPackagePolicies = (
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a list of Agent Policies
|
||||
* @param http
|
||||
* @param options
|
||||
*/
|
||||
export const sendGetAgentPolicyList = (
|
||||
http: HttpStart,
|
||||
options: HttpFetchOptions & GetAgentPoliciesRequest
|
||||
) => {
|
||||
return http.get<GetAgentPoliciesResponse>(INGEST_API_AGENT_POLICIES, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a list of Agent Policies
|
||||
* @param http
|
||||
* @param options
|
||||
*/
|
||||
export const sendBulkGetAgentPolicyList = (
|
||||
http: HttpStart,
|
||||
ids: string[],
|
||||
options: HttpFetchOptions = {}
|
||||
) => {
|
||||
return http.post<GetAgentPoliciesResponse>(`${INGEST_API_AGENT_POLICIES}/_bulk_get`, {
|
||||
...options,
|
||||
body: JSON.stringify({
|
||||
ids,
|
||||
ignoreMissing: true,
|
||||
full: true,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a package policy
|
||||
*
|
||||
|
|
|
@ -28955,7 +28955,6 @@
|
|||
"xpack.securitySolution.endpoint.list.totalCount": "Affichage de {totalItemCount, plural, one {# point de terminaison} many {# points de terminaison} other {# points de terminaison}}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount.limited": "Affichage de {limit} sur {totalItemCount, plural, one {# point de terminaison} many {# points de terminaison} other {# points de terminaison}}",
|
||||
"xpack.securitySolution.endpoint.list.transformFailed.message": "Une transformation requise, {transformId}, est actuellement en échec. La plupart du temps, ce problème peut être corrigé par {transformsPage}. Pour une assistance supplémentaire, veuillez visitez la {docsPage}",
|
||||
"xpack.securitySolution.endpoint.policy.advanced.show": "Paramètres avancés pour {action}",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.backButtonLabel": "Retour à la politique {policyName}",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.content": "Aucun artefact n'est actuellement affecté à {policyName}. Affectez des artefacts maintenant, ou ajoutez-les et gérez-les sur la page des artefacts.",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.noPrivileges.content": "Aucun artefact n'est actuellement affecté à {policyName}.",
|
||||
|
@ -31590,7 +31589,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.blocklist.list.search.placeholder": "Rechercher sur les champs ci-dessous : nom, description, valeur",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation": "Activez la bascule pour enregistrer Elastic comme solution d'antivirus officielle pour le système d'exploitation Windows. Cela désactivera également Windows Defender.",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.osRestriction": "Restrictions",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle": "Enregistrer comme antivirus",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type": "Enregistrer comme antivirus",
|
||||
"xpack.securitySolution.endpoint.policy.details.attack_surface_reduction": "Réduction de la surface d’attaque",
|
||||
"xpack.securitySolution.endpoint.policy.details.attackSurfaceReduction.type": "Réduction de la surface d’attaque",
|
||||
|
@ -31600,7 +31598,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.details.behavior_protection": "Comportement malveillant",
|
||||
"xpack.securitySolution.endpoint.policy.details.cancel": "Annuler",
|
||||
"xpack.securitySolution.endpoint.policy.details.cloudDeploymentLInk": "déploiement sur le cloud",
|
||||
"xpack.securitySolution.endpoint.policy.details.credentialHardening.toggle": "Renforcement de l’identification",
|
||||
"xpack.securitySolution.endpoint.policy.details.detect": "Détecter",
|
||||
"xpack.securitySolution.endpoint.policy.details.detectionRulesLink": "règles de détection associées",
|
||||
"xpack.securitySolution.endpoint.policy.details.eventCollection": "Collection d'événements",
|
||||
|
@ -31668,7 +31665,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.multiStepOnboarding.learnMore": "En savoir plus",
|
||||
"xpack.securitySolution.endpoint.policy.multiStepOnboarding.title": "Nous enregistrerons votre intégration avec nos valeurs par défaut recommandées.",
|
||||
"xpack.securitySolution.endpoint.policy.protections.behavior": "Protections contre les comportements malveillants",
|
||||
"xpack.securitySolution.endpoint.policy.protections.blocklist": "Liste noire activée",
|
||||
"xpack.securitySolution.endpoint.policy.protections.malware": "Protections contre les malware",
|
||||
"xpack.securitySolution.endpoint.policy.protections.memory": "Protections de la mémoire contre les menaces",
|
||||
"xpack.securitySolution.endpoint.policy.protections.ransomware": "Protections contre les ransomware",
|
||||
|
@ -31701,7 +31697,6 @@
|
|||
"xpack.securitySolution.endpoint.policyDetails.agentsSummary.onlineTitle": "Intègre",
|
||||
"xpack.securitySolution.endpoint.policyDetails.agentsSummary.totalTitle": "Total des agents",
|
||||
"xpack.securitySolution.endpoint.policyDetails.artifacts.title": "Artefacts",
|
||||
"xpack.securitySolution.endpoint.policyDetails.loadError": "Impossible de charger les paramètres de la politique des points de terminaison",
|
||||
"xpack.securitySolution.endpoint.policyDetails.settings.title": "Paramètres de politique",
|
||||
"xpack.securitySolution.endpoint.policyDetails.userNotification.placeholder": "Saisir votre message de notification personnalisé",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip": "Active ou désactive la liste noire associée à cette politique. La liste noire est une collection de hachages, de chemins ou de signataires qui étend la liste de processus considérés comme malveillants par le point de terminaison. Consultez l'onglet de la liste noire pour plus de détails sur l'entrée.",
|
||||
|
@ -31710,10 +31705,8 @@
|
|||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file": "Fichier",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network": "Réseau",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process": "Processus",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data": "Collecter les données de session",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.description": "Activez cette option pour capturer les données de processus étendues requises pour la vue de session. La vue de session vous fournit une représentation visuelle des données de session et d'exécution du processus. Les données de la vue de session sont organisées en fonction du modèle de processus Linux pour vous aider à examiner l'activité des processus, des utilisateurs et des services dans votre infrastructure Linux.",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.title": "Données de session",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io": "Capturer la sortie du terminal",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io.tooltip": "Activez cette option pour collecter la sortie du terminal (tty). La sortie du terminal apparaît dans la vue de session, et vous pouvez l'afficher séparément pour voir quelles commandes ont été exécutées et comment elles ont été tapées, à condition que le terminal soit en mode écho. Fonctionne uniquement sur les hôtes qui prennent en charge ebpf.",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file": "Fichier",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network": "Réseau",
|
||||
|
|
|
@ -28940,7 +28940,6 @@
|
|||
"xpack.securitySolution.endpoint.list.totalCount": "{totalItemCount, plural, other {#個のエンドポイント}}を表示中",
|
||||
"xpack.securitySolution.endpoint.list.totalCount.limited": "{limit}/{totalItemCount, plural, other {#個のエンドポイント}}ページを表示中",
|
||||
"xpack.securitySolution.endpoint.list.transformFailed.message": "現在、必須のトランスフォーム{transformId}が失敗しています。通常、これは{transformsPage}で修正できます。ヘルプについては、{docsPage}ご覧ください",
|
||||
"xpack.securitySolution.endpoint.policy.advanced.show": "{action}高度な設定",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.backButtonLabel": "{policyName}ポリシーに戻る",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.content": "現在、{policyName}に割り当てられたアーティファクトがありません。今すぐアーティファクトを割り当てるか、アーティファクトページでアーティファクトを追加および管理してください。",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.noPrivileges.content": "現在、{policyName}に割り当てられたアーティファクトがありません。",
|
||||
|
@ -31575,7 +31574,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.blocklist.list.search.placeholder": "次のフィールドで検索:名前、説明、値",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation": "オンにすると、ElasticをWindows OSのオフィシャルウイルス対策ソリューションとして登録します。これで Windows Defender も無効になります。",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.osRestriction": "制限事項",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle": "ウイルス対策として登録",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type": "ウイルス対策として登録",
|
||||
"xpack.securitySolution.endpoint.policy.details.attack_surface_reduction": "攻撃面削減",
|
||||
"xpack.securitySolution.endpoint.policy.details.attackSurfaceReduction.type": "攻撃面削減",
|
||||
|
@ -31585,7 +31583,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.details.behavior_protection": "悪意のある動作",
|
||||
"xpack.securitySolution.endpoint.policy.details.cancel": "キャンセル",
|
||||
"xpack.securitySolution.endpoint.policy.details.cloudDeploymentLInk": "クラウド展開",
|
||||
"xpack.securitySolution.endpoint.policy.details.credentialHardening.toggle": "資格情報強化",
|
||||
"xpack.securitySolution.endpoint.policy.details.detect": "検知",
|
||||
"xpack.securitySolution.endpoint.policy.details.detectionRulesLink": "関連する検出ルール",
|
||||
"xpack.securitySolution.endpoint.policy.details.eventCollection": "イベント収集",
|
||||
|
@ -31653,7 +31650,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.multiStepOnboarding.learnMore": "詳細",
|
||||
"xpack.securitySolution.endpoint.policy.multiStepOnboarding.title": "推奨のデフォルト値で統合が保存されます。",
|
||||
"xpack.securitySolution.endpoint.policy.protections.behavior": "悪意ある動作に対する保護",
|
||||
"xpack.securitySolution.endpoint.policy.protections.blocklist": "ブロックリストが有効にされました",
|
||||
"xpack.securitySolution.endpoint.policy.protections.malware": "マルウェア保護",
|
||||
"xpack.securitySolution.endpoint.policy.protections.memory": "メモリ脅威に対する保護",
|
||||
"xpack.securitySolution.endpoint.policy.protections.ransomware": "ランサムウェア保護",
|
||||
|
@ -31686,7 +31682,6 @@
|
|||
"xpack.securitySolution.endpoint.policyDetails.agentsSummary.onlineTitle": "正常",
|
||||
"xpack.securitySolution.endpoint.policyDetails.agentsSummary.totalTitle": "合計エージェント数",
|
||||
"xpack.securitySolution.endpoint.policyDetails.artifacts.title": "アーチファクト",
|
||||
"xpack.securitySolution.endpoint.policyDetails.loadError": "エンドポイントポリシー設定を読み込めませんでした",
|
||||
"xpack.securitySolution.endpoint.policyDetails.settings.title": "ポリシー設定",
|
||||
"xpack.securitySolution.endpoint.policyDetails.userNotification.placeholder": "カスタム通知メッセージを入力",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip": "このポリシーに関連付けられたブロックリストを有効または無効にします。このブロックリストは、コレクションハッシュ、パス、または署名者です。これはエンドポイントによって悪意があると見なされるプロセスのリストを拡張します。エントリ詳細については、ブロックリストタブを参照してください。",
|
||||
|
@ -31695,10 +31690,8 @@
|
|||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file": "ファイル",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network": "ネットワーク",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process": "プロセス",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data": "セッションデータを収集",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.description": "オンにすると、セッションビューに必要な拡張プロセスデータを取り込みます。セッションビューでは、セッションおよびプロセス実行データが視覚的に表示されます。セッションビューデータは、Linuxプロセスモデルに従って整理して表示され、Linuxインフラストラクチャーのプロセス、ユーザー、サービスアクティビティを調査できます。",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.title": "セッションデータ",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io": "ターミナル出力を取り込む",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io.tooltip": "オンにすると、ターミナル(tty)出力を収集します。ターミナル出力はセッションビューに表示されます。ターミナルがエコーモードの場合は、実行されたコマンド、入力方法を個別に表示して確認できます。ebpfをサポートするホストでのみ動作します。",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file": "ファイル",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network": "ネットワーク",
|
||||
|
|
|
@ -28936,7 +28936,6 @@
|
|||
"xpack.securitySolution.endpoint.list.totalCount": "正在显示 {totalItemCount, plural, other {# 个终端}}",
|
||||
"xpack.securitySolution.endpoint.list.totalCount.limited": "正在显示 {limit} 个,共 {totalItemCount, plural, other {# 个终端}} 个",
|
||||
"xpack.securitySolution.endpoint.list.transformFailed.message": "所需的转换 {transformId} 当前失败。多数时候,这可以通过 {transformsPage} 解决。要获取更多帮助,请访问{docsPage}",
|
||||
"xpack.securitySolution.endpoint.policy.advanced.show": "{action} 高级设置",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.backButtonLabel": "返回到 {policyName} 策略",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.content": "当前没有项目已分配给 {policyName}。立即分配项目,或在项目页面上添加和管理项目。",
|
||||
"xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.noPrivileges.content": "当前没有项目已分配给 {policyName}。",
|
||||
|
@ -31571,7 +31570,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.blocklist.list.search.placeholder": "搜索下面的字段:name、description、value",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation": "打开可将 Elastic 注册为 Windows 操作系统的正式防病毒解决方案。这也将禁用 Windows Defender。",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.osRestriction": "限制",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle": "注册为防病毒解决方案",
|
||||
"xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type": "注册为防病毒解决方案",
|
||||
"xpack.securitySolution.endpoint.policy.details.attack_surface_reduction": "攻击面减少",
|
||||
"xpack.securitySolution.endpoint.policy.details.attackSurfaceReduction.type": "攻击面减少",
|
||||
|
@ -31581,7 +31579,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.details.behavior_protection": "恶意行为",
|
||||
"xpack.securitySolution.endpoint.policy.details.cancel": "取消",
|
||||
"xpack.securitySolution.endpoint.policy.details.cloudDeploymentLInk": "云部署",
|
||||
"xpack.securitySolution.endpoint.policy.details.credentialHardening.toggle": "凭据强化",
|
||||
"xpack.securitySolution.endpoint.policy.details.detect": "检测",
|
||||
"xpack.securitySolution.endpoint.policy.details.detectionRulesLink": "相关检测规则",
|
||||
"xpack.securitySolution.endpoint.policy.details.eventCollection": "事件收集",
|
||||
|
@ -31649,7 +31646,6 @@
|
|||
"xpack.securitySolution.endpoint.policy.multiStepOnboarding.learnMore": "了解详情",
|
||||
"xpack.securitySolution.endpoint.policy.multiStepOnboarding.title": "我们将使用建议的默认值保存您的集成。",
|
||||
"xpack.securitySolution.endpoint.policy.protections.behavior": "恶意行为防护",
|
||||
"xpack.securitySolution.endpoint.policy.protections.blocklist": "阻止列表已启用",
|
||||
"xpack.securitySolution.endpoint.policy.protections.malware": "恶意软件防护",
|
||||
"xpack.securitySolution.endpoint.policy.protections.memory": "内存威胁防护",
|
||||
"xpack.securitySolution.endpoint.policy.protections.ransomware": "勒索软件防护",
|
||||
|
@ -31682,7 +31678,6 @@
|
|||
"xpack.securitySolution.endpoint.policyDetails.agentsSummary.onlineTitle": "运行正常",
|
||||
"xpack.securitySolution.endpoint.policyDetails.agentsSummary.totalTitle": "代理总数",
|
||||
"xpack.securitySolution.endpoint.policyDetails.artifacts.title": "项目",
|
||||
"xpack.securitySolution.endpoint.policyDetails.loadError": "无法加载终端策略设置",
|
||||
"xpack.securitySolution.endpoint.policyDetails.settings.title": "策略设置",
|
||||
"xpack.securitySolution.endpoint.policyDetails.userNotification.placeholder": "输入您的定制通知消息",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip": "启用或禁用与此策略关联的阻止列表。阻止列表是哈希、路径或签名者的集合,它扩充了终端视为恶意的进程列表。查看阻止列表选项卡了解条目详情。",
|
||||
|
@ -31691,10 +31686,8 @@
|
|||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file": "文件",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network": "网络",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process": "进程",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data": "收集会话数据",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.description": "打开此项可捕获会话视图所需的扩展进程数据。会话视图为您提供了会话和进程执行数据的视觉表示形式。会话视图数据将根据 Linux 进程模型进行组织,以帮助您调查 Linux 基础架构上的进程、用户和服务活动。",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.session_data.title": "会话数据",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io": "捕获终端输出",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.tty_io.tooltip": "打开此项可收集终端 (tty) 输出。终端输出在会话视图中显示,只要终端处于回显模式,您就可以单独查看该输出来了解执行了哪些命令、如何键入这些命令。仅在支持 ebpf 的主机上运行。",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file": "文件",
|
||||
"xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network": "网络",
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data';
|
||||
import { popupVersionsMap } from '@kbn/security-solution-plugin/public/management/pages/policy/view/policy_forms/protections/popup_options_to_versions';
|
||||
import { PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION } from '@kbn/security-solution-plugin/public/management/pages/policy/view/policy_settings_form/protection_notice_supported_endpoint_version';
|
||||
import { getPolicySettingsFormTestSubjects } from '@kbn/security-solution-plugin/public/management/pages/policy/view/policy_settings_form/mocks';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { PolicyTestResourceInfo } from '../../services/endpoint_policy';
|
||||
|
||||
|
@ -28,6 +29,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
describe('When on the Endpoint Policy Details Page', function () {
|
||||
let indexedData: IndexedHostsAndAlertsResponse;
|
||||
const formTestSubjects = getPolicySettingsFormTestSubjects();
|
||||
|
||||
before(async () => {
|
||||
indexedData = await endpointTestResources.loadEndpointData();
|
||||
|
@ -80,91 +82,94 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.policy.navigateToPolicyDetails(policyInfo.packagePolicy.id);
|
||||
});
|
||||
|
||||
it('and the show advanced settings button is clicked', async () => {
|
||||
await testSubjects.missingOrFail('advancedPolicyPanel');
|
||||
it('Should show/hide advanced section when button is clicked', async () => {
|
||||
await testSubjects.missingOrFail(formTestSubjects.advancedSection.settingsContainer);
|
||||
|
||||
// Expand
|
||||
await pageObjects.policy.showAdvancedSettingsSection();
|
||||
await testSubjects.existOrFail('advancedPolicyPanel');
|
||||
await testSubjects.existOrFail(formTestSubjects.advancedSection.settingsContainer);
|
||||
|
||||
// Collapse
|
||||
await pageObjects.policy.hideAdvancedSettingsSection();
|
||||
await testSubjects.missingOrFail('advancedPolicyPanel');
|
||||
await testSubjects.missingOrFail(formTestSubjects.advancedSection.settingsContainer);
|
||||
});
|
||||
});
|
||||
|
||||
['malware', 'ransomware'].forEach((protection) => {
|
||||
describe(`on the ${protection} protections section`, () => {
|
||||
describe(`on the ${protection} protections card`, () => {
|
||||
let policyInfo: PolicyTestResourceInfo;
|
||||
const cardTestSubj:
|
||||
| typeof formTestSubjects['ransomware']
|
||||
| typeof formTestSubjects['malware'] =
|
||||
formTestSubjects[
|
||||
protection as keyof Pick<typeof formTestSubjects, 'malware' | 'ransomware'>
|
||||
];
|
||||
|
||||
beforeEach(async () => {
|
||||
policyInfo = await policyTestResources.createPolicy();
|
||||
await pageObjects.policy.navigateToPolicyDetails(policyInfo.packagePolicy.id);
|
||||
await testSubjects.existOrFail(`${protection}ProtectionsForm`);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (policyInfo) {
|
||||
await policyInfo.cleanup();
|
||||
|
||||
// @ts-expect-error forcing to undefined
|
||||
policyInfo = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it('should show the supported Endpoint version', async () => {
|
||||
const supportedVersionElement = await testSubjects.findDescendant(
|
||||
'policySupportedVersions',
|
||||
await testSubjects.find(`${protection}ProtectionsForm`)
|
||||
);
|
||||
|
||||
expect(await supportedVersionElement.getVisibleText()).to.equal(
|
||||
'Agent version ' + popupVersionsMap.get(protection)
|
||||
it('should show the supported Endpoint version for user notification', async () => {
|
||||
expect(await testSubjects.getVisibleText(cardTestSubj.notifySupportedVersion)).to.equal(
|
||||
'Agent version ' +
|
||||
PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION[
|
||||
protection as keyof typeof PROTECTION_NOTICE_SUPPORTED_ENDPOINT_VERSION
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should show the custom message text area when the Notify User checkbox is checked', async () => {
|
||||
expect(await testSubjects.isChecked(`${protection}UserNotificationCheckbox`)).to.be(true);
|
||||
await testSubjects.existOrFail(`${protection}UserNotificationCustomMessage`);
|
||||
expect(await testSubjects.isChecked(cardTestSubj.notifyUserCheckbox)).to.be(true);
|
||||
await testSubjects.existOrFail(cardTestSubj.notifyCustomMessage);
|
||||
});
|
||||
|
||||
it('should not show the custom message text area when the Notify User checkbox is unchecked', async () => {
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
`${protection}UserNotificationCheckbox`
|
||||
);
|
||||
expect(await testSubjects.isChecked(`${protection}UserNotificationCheckbox`)).to.be(
|
||||
false
|
||||
);
|
||||
await testSubjects.missingOrFail(`${protection}UserNotificationCustomMessage`);
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox(cardTestSubj.notifyUserCheckbox);
|
||||
expect(await testSubjects.isChecked(cardTestSubj.notifyUserCheckbox)).to.be(false);
|
||||
await testSubjects.missingOrFail(cardTestSubj.notifyCustomMessage);
|
||||
});
|
||||
|
||||
it('should show a sample custom message', async () => {
|
||||
const customMessageBox = await testSubjects.find(
|
||||
`${protection}UserNotificationCustomMessage`
|
||||
);
|
||||
expect(await customMessageBox.getVisibleText()).equal(
|
||||
expect(await testSubjects.getVisibleText(cardTestSubj.notifyCustomMessage)).equal(
|
||||
'Elastic Security {action} {filename}'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show a tooltip ', async () => {
|
||||
const malwareTooltipIcon = await testSubjects.find(`${protection}TooltipIcon`);
|
||||
await malwareTooltipIcon.moveMouseTo();
|
||||
it('should show a tooltip on hover', async () => {
|
||||
await testSubjects.moveMouseTo(cardTestSubj.notifyCustomMessageTooltipIcon);
|
||||
|
||||
const malwareTooltip = await testSubjects.find(`${protection}Tooltip`);
|
||||
expect(await malwareTooltip.getVisibleText()).equal(
|
||||
expect(
|
||||
await testSubjects.getVisibleText(cardTestSubj.notifyCustomMessageTooltipInfo)
|
||||
).equal(
|
||||
`Selecting the user notification option will display a notification to the host user when ${protection} is prevented or detected.\nThe user notification can be customized in the text box below. Bracketed tags can be used to dynamically populate the applicable action (such as prevented or detected) and the filename.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve a custom notification message upon saving', async () => {
|
||||
const customMessageBox = await testSubjects.find(
|
||||
`${protection}UserNotificationCustomMessage`
|
||||
await testSubjects.setValue(cardTestSubj.notifyCustomMessage, '', {
|
||||
clearWithKeyboard: true,
|
||||
});
|
||||
await testSubjects.setValue(
|
||||
cardTestSubj.notifyCustomMessage,
|
||||
'a custom notification message @$% 123',
|
||||
{ typeCharByChar: true }
|
||||
);
|
||||
await customMessageBox.clearValue();
|
||||
await customMessageBox.type('a custom notification message @$% 123');
|
||||
|
||||
await pageObjects.policy.confirmAndSave();
|
||||
await testSubjects.existOrFail('policyDetailsSuccessMessage');
|
||||
expect(
|
||||
await testSubjects.getVisibleText(`${protection}UserNotificationCustomMessage`)
|
||||
).to.equal('a custom notification message @$% 123');
|
||||
expect(await testSubjects.getVisibleText(cardTestSubj.notifyCustomMessage)).to.equal(
|
||||
'a custom notification message @$% 123'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -180,21 +185,28 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
afterEach(async () => {
|
||||
if (policyInfo) {
|
||||
await policyInfo.cleanup();
|
||||
|
||||
// @ts-expect-error forcing to undefined
|
||||
policyInfo = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it('should display success toast on successful save', async () => {
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox
|
||||
);
|
||||
await pageObjects.policy.confirmAndSave();
|
||||
|
||||
await testSubjects.existOrFail('policyDetailsSuccessMessage');
|
||||
expect(await testSubjects.getVisibleText('policyDetailsSuccessMessage')).to.equal(
|
||||
`Integration ${policyInfo.packagePolicy.name} has been updated.`
|
||||
`Success!\nIntegration ${policyInfo.packagePolicy.name} has been updated.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should persist update on the screen', async () => {
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_process');
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
formTestSubjects.windowsEvents.processCheckbox
|
||||
);
|
||||
await pageObjects.policy.confirmAndSave();
|
||||
|
||||
await testSubjects.existOrFail('policyDetailsSuccessMessage');
|
||||
|
@ -202,9 +214,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.endpoint.navigateToEndpointList();
|
||||
await pageObjects.policy.navigateToPolicyDetails(policyInfo.packagePolicy.id);
|
||||
|
||||
expect(await (await testSubjects.find('policyWindowsEvent_process')).isSelected()).to.equal(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
await (
|
||||
await testSubjects.find(formTestSubjects.windowsEvents.processCheckbox)
|
||||
).isSelected()
|
||||
).to.equal(false);
|
||||
});
|
||||
|
||||
it('should have updated policy data in overall Agent Policy', async () => {
|
||||
|
@ -212,9 +226,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
|
||||
|
||||
await Promise.all([
|
||||
pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_file'),
|
||||
pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyLinuxEvent_file'),
|
||||
pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyMacEvent_file'),
|
||||
pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
formTestSubjects.windowsEvents.fileCheckbox
|
||||
),
|
||||
pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
formTestSubjects.linuxEvents.fileCheckbox
|
||||
),
|
||||
pageObjects.endpointPageUtils.clickOnEuiCheckbox(formTestSubjects.macEvents.fileCheckbox),
|
||||
]);
|
||||
|
||||
await pageObjects.policy.showAdvancedSettingsSection();
|
||||
|
@ -290,7 +308,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
policyInfo.agentPolicy.id,
|
||||
policyInfo.packagePolicy.id
|
||||
);
|
||||
await testSubjects.existOrFail('endpointIntegrationPolicyForm');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -300,27 +317,41 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should show the endpoint policy form', async () => {
|
||||
await testSubjects.existOrFail('endpointIntegrationPolicyForm');
|
||||
await testSubjects.existOrFail(formTestSubjects.form);
|
||||
});
|
||||
|
||||
it('should allow updates to policy items', async () => {
|
||||
const winDnsEventingCheckbox = await testSubjects.find('policyWindowsEvent_dns');
|
||||
const winDnsEventingCheckbox = await testSubjects.find(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox
|
||||
);
|
||||
await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow(
|
||||
winDnsEventingCheckbox
|
||||
);
|
||||
expect(await winDnsEventingCheckbox.isSelected()).to.be(true);
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
|
||||
await pageObjects.policy.waitForCheckboxSelectionChange('policyWindowsEvent_dns', false);
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox
|
||||
);
|
||||
await pageObjects.policy.waitForCheckboxSelectionChange(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox,
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should include updated endpoint data when saved', async () => {
|
||||
await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow(
|
||||
await testSubjects.find('policyWindowsEvent_dns')
|
||||
await testSubjects.find(formTestSubjects.windowsEvents.dnsCheckbox)
|
||||
);
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox
|
||||
);
|
||||
const updatedCheckboxValue = await testSubjects.isSelected(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox
|
||||
);
|
||||
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
|
||||
const updatedCheckboxValue = await testSubjects.isSelected('policyWindowsEvent_dns');
|
||||
|
||||
await pageObjects.policy.waitForCheckboxSelectionChange('policyWindowsEvent_dns', false);
|
||||
await pageObjects.policy.waitForCheckboxSelectionChange(
|
||||
formTestSubjects.windowsEvents.dnsCheckbox,
|
||||
false
|
||||
);
|
||||
|
||||
await (await pageObjects.ingestManagerCreatePackagePolicy.findSaveButton(true)).click();
|
||||
await pageObjects.ingestManagerCreatePackagePolicy.waitForSaveSuccessNotification(true);
|
||||
|
@ -331,7 +362,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
await pageObjects.policy.waitForCheckboxSelectionChange(
|
||||
'policyWindowsEvent_dns',
|
||||
formTestSubjects.windowsEvents.dnsCheckbox,
|
||||
updatedCheckboxValue
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { getPolicySettingsFormTestSubjects } from '@kbn/security-solution-plugin/public/management/pages/policy/view/policy_settings_form/mocks';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const pageObjects = getPageObjects(['common', 'header']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retryService = getService('retry');
|
||||
const formTestSubj = getPolicySettingsFormTestSubjects();
|
||||
|
||||
return {
|
||||
/**
|
||||
|
@ -59,16 +61,8 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
|
|||
return await testSubjects.find('policyDetailsCancelButton');
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds and returns the Advanced Policy Show/Hide Button
|
||||
*/
|
||||
async findAdvancedPolicyButton() {
|
||||
await this.ensureIsOnDetailsPage();
|
||||
return await testSubjects.find('advancedPolicyButton');
|
||||
},
|
||||
|
||||
async isAdvancedSettingsExpanded() {
|
||||
return await testSubjects.exists('advancedPolicyPanel');
|
||||
return await testSubjects.exists(formTestSubj.advancedSection.settingsContainer);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -76,12 +70,9 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
|
|||
*/
|
||||
async showAdvancedSettingsSection() {
|
||||
if (!(await this.isAdvancedSettingsExpanded())) {
|
||||
const expandButton = await this.findAdvancedPolicyButton();
|
||||
await expandButton.click();
|
||||
await testSubjects.click(formTestSubj.advancedSection.showHideButton);
|
||||
}
|
||||
|
||||
await testSubjects.existOrFail('advancedPolicyPanel');
|
||||
await testSubjects.scrollIntoView('advancedPolicyPanel');
|
||||
await testSubjects.scrollIntoView(formTestSubj.advancedSection.settingsContainer);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -89,10 +80,9 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
|
|||
*/
|
||||
async hideAdvancedSettingsSection() {
|
||||
if (await this.isAdvancedSettingsExpanded()) {
|
||||
const expandButton = await this.findAdvancedPolicyButton();
|
||||
await expandButton.click();
|
||||
await testSubjects.click(formTestSubj.advancedSection.showHideButton);
|
||||
}
|
||||
await testSubjects.missingOrFail('advancedPolicyPanel');
|
||||
await testSubjects.missingOrFail(formTestSubj.advancedSection.settingsContainer);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -104,7 +94,7 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
|
|||
},
|
||||
|
||||
/**
|
||||
* ensures that the Details Page is the currently display view
|
||||
* ensures that the Details Page is currently displayed
|
||||
*/
|
||||
async ensureIsOnDetailsPage() {
|
||||
await testSubjects.existOrFail('policyDetailsPage');
|
||||
|
@ -116,8 +106,6 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
|
|||
async confirmAndSave() {
|
||||
await this.ensureIsOnDetailsPage();
|
||||
|
||||
const saveButton = await this.findSaveButton();
|
||||
|
||||
// Sometimes, data retrieval errors may have been encountered by other security solution processes
|
||||
// (ex. index fields search here: `x-pack/plugins/security_solution/public/common/containers/source/index.tsx:181`)
|
||||
// which are displayed using one or more Toast messages. This in turn prevents the user from
|
||||
|
@ -125,31 +113,11 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
|
|||
// we'll first check that all toasts are cleared
|
||||
await pageObjects.common.clearAllToasts();
|
||||
|
||||
await saveButton.click();
|
||||
await testSubjects.click('policyDetailsSaveButton');
|
||||
await testSubjects.existOrFail('policyDetailsConfirmModal');
|
||||
await pageObjects.common.clickConfirmOnModal();
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds and returns the Create New policy Policy button displayed on the List page
|
||||
*/
|
||||
async findHeaderCreateNewButton() {
|
||||
// The Create button is initially disabled because we need to first make a call to Ingest
|
||||
// to retrieve the package version, so that the redirect works as expected. So, we wait
|
||||
// for that to occur here a well.
|
||||
await testSubjects.waitForEnabled('headerCreateNewPolicyButton');
|
||||
return await testSubjects.find('headerCreateNewPolicyButton');
|
||||
},
|
||||
|
||||
/**
|
||||
* Used when looking a the Ingest create/edit package policy pages. Finds the endpoint
|
||||
* custom configuration component
|
||||
* @param onEditPage
|
||||
*/
|
||||
async findPackagePolicyEndpointCustomConfiguration(onEditPage: boolean = false) {
|
||||
return await testSubjects.find(`endpointPackagePolicy_${onEditPage ? 'edit' : 'create'}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for a Checkbox/Radiobutton to have its `isSelected()` value match the provided expected value
|
||||
* @param selector
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue