Skip to content

Commit

Permalink
[Security Solution][Endpoint] Enable Kibana feature controls (RBAC)co…
Browse files Browse the repository at this point in the history
…nfiguration by space for Endpoint management (#193003)

## Summary

### Kibana Core Security plugin

- Updated several Role forms to include `data-test-subj` in order to
better select items from tests



### Security Solution Plugin (and associated packages)

Changes in support of space awareness:

- New feature flag control: `endpointManagementSpaceAwarenessEnabled`
- Elastic Defend related kibana feature controls are changed to
`requireAllSpaces: false` when feature flag is enabled

In addition:

- The Cypress serverless FTR configuration used for Defend Workflows
tests was updated to enable the creation of spaces and also role
management UI
  • Loading branch information
paul-tavares committed Sep 20, 2024
1 parent 5ea37fb commit 9a9c0f1
Show file tree
Hide file tree
Showing 15 changed files with 517 additions and 55 deletions.
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
10 changes: 10 additions & 0 deletions x-pack/plugins/security_solution/common/experimental_features.ts
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

0 comments on commit 9a9c0f1

Please sign in to comment.