Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Security Solution][Endpoint] Enable Kibana feature controls (RBAC)configuration by space for Endpoint management (#193003) #193525

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { SecuritySubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants';
import type { SecurityFeatureParams } from './types';

const endpointListSubFeature: SubFeatureConfig = {
const endpointListSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList.privilegesTooltip',
Expand Down Expand Up @@ -67,8 +67,9 @@ const endpointListSubFeature: SubFeatureConfig = {
],
},
],
};
const trustedApplicationsSubFeature: SubFeatureConfig = {
});

const trustedApplicationsSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications.privilegesTooltip',
Expand Down Expand Up @@ -124,8 +125,8 @@ const trustedApplicationsSubFeature: SubFeatureConfig = {
],
},
],
};
const hostIsolationExceptionsBasicSubFeature: SubFeatureConfig = {
});
const hostIsolationExceptionsBasicSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip',
Expand Down Expand Up @@ -181,8 +182,8 @@ const hostIsolationExceptionsBasicSubFeature: SubFeatureConfig = {
],
},
],
};
const blocklistSubFeature: SubFeatureConfig = {
});
const blocklistSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.blockList.privilegesTooltip',
Expand Down Expand Up @@ -235,8 +236,8 @@ const blocklistSubFeature: SubFeatureConfig = {
],
},
],
};
const eventFiltersSubFeature: SubFeatureConfig = {
});
const eventFiltersSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters.privilegesTooltip',
Expand Down Expand Up @@ -292,8 +293,8 @@ const eventFiltersSubFeature: SubFeatureConfig = {
],
},
],
};
const policyManagementSubFeature: SubFeatureConfig = {
});
const policyManagementSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement.privilegesTooltip',
Expand Down Expand Up @@ -343,9 +344,9 @@ const policyManagementSubFeature: SubFeatureConfig = {
],
},
],
};
});

const responseActionsHistorySubFeature: SubFeatureConfig = {
const responseActionsHistorySubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip',
Expand Down Expand Up @@ -394,8 +395,8 @@ const responseActionsHistorySubFeature: SubFeatureConfig = {
],
},
],
};
const hostIsolationSubFeature: SubFeatureConfig = {
});
const hostIsolationSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation.privilegesTooltip',
Expand Down Expand Up @@ -431,9 +432,9 @@ const hostIsolationSubFeature: SubFeatureConfig = {
],
},
],
};
});

const processOperationsSubFeature: SubFeatureConfig = {
const processOperationsSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations.privilegesTooltip',
Expand Down Expand Up @@ -471,8 +472,8 @@ const processOperationsSubFeature: SubFeatureConfig = {
],
},
],
};
const fileOperationsSubFeature: SubFeatureConfig = {
});
const fileOperationsSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations.privilegesTooltip',
Expand Down Expand Up @@ -510,11 +511,11 @@ const fileOperationsSubFeature: SubFeatureConfig = {
],
},
],
};
});

// execute operations are not available in 8.7,
// but will be available in 8.8
const executeActionSubFeature: SubFeatureConfig = {
const executeActionSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations.privilegesTooltip',
Expand Down Expand Up @@ -552,10 +553,10 @@ const executeActionSubFeature: SubFeatureConfig = {
],
},
],
};
});

// 8.15 feature
const scanActionSubFeature: SubFeatureConfig = {
const scanActionSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.scanOperations.privilegesTooltip',
Expand Down Expand Up @@ -593,9 +594,9 @@ const scanActionSubFeature: SubFeatureConfig = {
],
},
],
};
});

const endpointExceptionsSubFeature: SubFeatureConfig = {
const endpointExceptionsSubFeature = (): SubFeatureConfig => ({
requireAllSpaces: true,
privilegesTooltip: i18n.translate(
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions.privilegesTooltip',
Expand Down Expand Up @@ -642,7 +643,7 @@ const endpointExceptionsSubFeature: SubFeatureConfig = {
],
},
],
};
});

/**
* Sub-features that will always be available for Security
Expand All @@ -660,20 +661,47 @@ export const getSecurityBaseKibanaSubFeatureIds = (
export const getSecuritySubFeaturesMap = ({
experimentalFeatures,
}: SecurityFeatureParams): Map<SecuritySubFeatureId, SubFeatureConfig> => {
const enableSpaceAwarenessIfNeeded = (subFeature: SubFeatureConfig): SubFeatureConfig => {
if (experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
subFeature.requireAllSpaces = false;
subFeature.privilegesTooltip = undefined;
}

return subFeature;
};

const securitySubFeaturesList: Array<[SecuritySubFeatureId, SubFeatureConfig]> = [
[SecuritySubFeatureId.endpointList, endpointListSubFeature],
[SecuritySubFeatureId.endpointExceptions, endpointExceptionsSubFeature],
[SecuritySubFeatureId.trustedApplications, trustedApplicationsSubFeature],
[SecuritySubFeatureId.hostIsolationExceptionsBasic, hostIsolationExceptionsBasicSubFeature],
[SecuritySubFeatureId.blocklist, blocklistSubFeature],
[SecuritySubFeatureId.eventFilters, eventFiltersSubFeature],
[SecuritySubFeatureId.policyManagement, policyManagementSubFeature],
[SecuritySubFeatureId.responseActionsHistory, responseActionsHistorySubFeature],
[SecuritySubFeatureId.hostIsolation, hostIsolationSubFeature],
[SecuritySubFeatureId.processOperations, processOperationsSubFeature],
[SecuritySubFeatureId.fileOperations, fileOperationsSubFeature],
[SecuritySubFeatureId.executeAction, executeActionSubFeature],
[SecuritySubFeatureId.scanAction, scanActionSubFeature],
[SecuritySubFeatureId.endpointList, enableSpaceAwarenessIfNeeded(endpointListSubFeature())],
[
SecuritySubFeatureId.endpointExceptions,
enableSpaceAwarenessIfNeeded(endpointExceptionsSubFeature()),
],
[
SecuritySubFeatureId.trustedApplications,
enableSpaceAwarenessIfNeeded(trustedApplicationsSubFeature()),
],
[
SecuritySubFeatureId.hostIsolationExceptionsBasic,
enableSpaceAwarenessIfNeeded(hostIsolationExceptionsBasicSubFeature()),
],
[SecuritySubFeatureId.blocklist, enableSpaceAwarenessIfNeeded(blocklistSubFeature())],
[SecuritySubFeatureId.eventFilters, enableSpaceAwarenessIfNeeded(eventFiltersSubFeature())],
[
SecuritySubFeatureId.policyManagement,
enableSpaceAwarenessIfNeeded(policyManagementSubFeature()),
],
[
SecuritySubFeatureId.responseActionsHistory,
enableSpaceAwarenessIfNeeded(responseActionsHistorySubFeature()),
],
[SecuritySubFeatureId.hostIsolation, enableSpaceAwarenessIfNeeded(hostIsolationSubFeature())],
[
SecuritySubFeatureId.processOperations,
enableSpaceAwarenessIfNeeded(processOperationsSubFeature()),
],
[SecuritySubFeatureId.fileOperations, enableSpaceAwarenessIfNeeded(fileOperationsSubFeature())],
[SecuritySubFeatureId.executeAction, enableSpaceAwarenessIfNeeded(executeActionSubFeature())],
[SecuritySubFeatureId.scanAction, enableSpaceAwarenessIfNeeded(scanActionSubFeature())],
];

// Use the following code to add feature based on feature flag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import type { ProductFeatureSecurityKey, SecuritySubFeatureId } from '../product
import type { ProductFeatureKibanaConfig } from '../types';

export interface SecurityFeatureParams {
/**
* Experimental features.
* Unfortunately these can't be properly Typed due to it requiring an
* import directly from the Security Solution plugin. The list of `keys` in this
* object are defined here:
* @see https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/common/experimental_features.ts#L14
*/
experimentalFeatures: Record<string, boolean>;
savedObjects: string[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export class FeatureTable extends Component<Props, State> {
arrowDisplay={canExpandCategory ? 'left' : 'none'}
forceState={canExpandCategory ? undefined : 'closed'}
buttonContent={buttonContent}
buttonProps={{ 'data-test-subj': `featureCategory_${category.id}_accordionToggle` }}
extraAction={canExpandCategory ? extraAction : undefined}
>
<div>
Expand All @@ -162,7 +163,10 @@ export class FeatureTable extends Component<Props, State> {
)}
<EuiFlexGroup direction="column" gutterSize="s">
{featuresInCategory.map((feature) => (
<EuiFlexItem key={feature.id}>
<EuiFlexItem
key={feature.id}
data-test-subj={`featureCategory_${category.id}_${feature.id}`}
>
{this.renderPrivilegeControlsForFeature(feature)}
</EuiFlexItem>
))}
Expand Down Expand Up @@ -229,6 +233,9 @@ export class FeatureTable extends Component<Props, State> {
data-test-subj="featurePrivilegeControls"
buttonContent={buttonContent}
buttonClassName="euiAccordionWithDescription"
buttonProps={{
'data-test-subj': `featurePrivilegeControls_${feature.category.id}_${feature.id}_accordionToggle`,
}}
extraAction={extraAction}
forceState={hasSubFeaturePrivileges ? undefined : 'closed'}
arrowDisplay={hasSubFeaturePrivileges ? 'left' : 'none'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,14 @@ export const FeatureTableExpandedRow = ({
};

return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiFlexGroup
direction="column"
gutterSize="s"
data-test-subj={`${feature.category.id}_${feature.id}_subFeaturesTable`}
>
<EuiFlexItem
data-test-subj={`${feature.category.id}_${feature.id}_customizeSubFeaturesSwitchContainer`}
>
<div>
<EuiSwitch
label={
Expand Down Expand Up @@ -121,6 +127,7 @@ export const FeatureTableExpandedRow = ({
privilegeCalculator={privilegeCalculator}
privilegeIndex={privilegeIndex}
featureId={feature.id}
categoryId={feature.category.id}
subFeature={subFeature}
onChange={(updatedPrivileges) => onChange(feature.id, updatedPrivileges)}
selectedFeaturePrivileges={selectedFeaturePrivileges}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
EuiIconTip,
EuiText,
} from '@elastic/eui';
import React from 'react';
import React, { useCallback, useMemo } from 'react';

import { i18n } from '@kbn/i18n';
import type {
Expand All @@ -33,10 +33,23 @@ interface Props {
privilegeIndex: number;
onChange: (selectedPrivileges: string[]) => void;
disabled?: boolean;
categoryId?: string;
}

export const SubFeatureForm = (props: Props) => {
const groupsWithPrivileges = props.subFeature.getPrivilegeGroups();
const subFeatureNameTestId = useMemo(() => {
// Removes anything that is not a Number, Letter or space and replaces it with _
return props.subFeature.name.toLowerCase().replace(/[^\w\d]/g, '_');
}, [props.subFeature.name]);
const getTestId = useCallback(
(suffix: string = '') => {
return `${props.categoryId ? `${props.categoryId}_` : ''}${
props.featureId
}_${subFeatureNameTestId}${suffix ? `_${suffix}` : ''}`;
},
[props.categoryId, props.featureId, subFeatureNameTestId]
);

const getTooltip = () => {
if (!props.subFeature.privilegesTooltip) {
Expand All @@ -55,6 +68,7 @@ export const SubFeatureForm = (props: Props) => {
type="iInCircle"
color="subdued"
content={tooltipContent}
anchorProps={{ 'data-test-subj': getTestId('nameTooltip') }}
/>
);
};
Expand All @@ -63,11 +77,11 @@ export const SubFeatureForm = (props: Props) => {
return null;
}
return (
<EuiFlexGroup alignItems="center">
<EuiFlexGroup alignItems="center" data-test-subj={getTestId()}>
<EuiFlexItem grow={3}>
<EuiFlexGroup gutterSize="none" direction="column">
<EuiFlexItem>
<EuiText size="s">
<EuiText size="s" data-test-subj={getTestId('name')}>
{props.subFeature.name} {getTooltip()}
</EuiText>
</EuiFlexItem>
Expand All @@ -85,7 +99,9 @@ export const SubFeatureForm = (props: Props) => {
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={2}>{groupsWithPrivileges.map(renderPrivilegeGroup)}</EuiFlexItem>
<EuiFlexItem grow={2} data-test-subj={getTestId('privilegeGroup')}>
{groupsWithPrivileges.map(renderPrivilegeGroup)}
</EuiFlexItem>
</EuiFlexGroup>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ export const allowedExperimentalValues = Object.freeze({
*/
responseActionsCrowdstrikeManualHostIsolationEnabled: true,

/**
* Space awareness for Elastic Defend management.
* Feature depends on Fleet's corresponding features also being enabled:
* - `subfeaturePrivileges`
* - `useSpaceAwareness`
* and Fleet must set it runtime mode to spaces by calling the following API:
* - `POST /internal/fleet/enable_space_awareness`
*/
endpointManagementSpaceAwarenessEnabled: false,

/**
* Enables new notes
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ describe('Endpoints page', { tags: ['@ess', '@serverless', '@brokenInServerless'
expect(body.sortDirection).to.equal('desc');
});

// no sorting indicator is present on the screen
cy.get('.euiTableSortIcon').should('not.exist');
// no column shows sorting to be on
cy.get('.euiTableHeaderButton-isSorted').should('not.exist');
});

it('User can sort by any field', () => {
Expand All @@ -71,12 +71,12 @@ describe('Endpoints page', { tags: ['@ess', '@serverless', '@brokenInServerless'
cy.getByTestSubj(`tableHeaderCell_${field}_${i}`).as('header').click();
validateSortingInResponse(field, 'asc');
cy.get('@header').should('have.attr', 'aria-sort', 'ascending');
cy.get('.euiTableSortIcon').should('exist');
cy.get('.euiTableHeaderButton-isSorted').should('exist');

cy.get('@header').click();
validateSortingInResponse(field, 'desc');
cy.get('@header').should('have.attr', 'aria-sort', 'descending');
cy.get('.euiTableSortIcon').should('exist');
cy.get('.euiTableHeaderButton-isSorted').should('exist');
}
});

Expand Down
Loading