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

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ffe877b
Update Fleet authz to give access to Policy List for endpoint list
paul-tavares Sep 13, 2024
5a1a2f5
Add feature flag `endpointManagementSpaceAwarenessEnabled`
paul-tavares Sep 13, 2024
87cd5ca
Update kibana feature controls to enable space awareness if FF is ena…
paul-tavares Sep 13, 2024
bb08b05
Revert change to Fleet authz for package policy list api
paul-tavares Sep 13, 2024
cf67151
Merge remote-tracking branch 'upstream/main' into task/olm-8537-rbac-…
paul-tavares Sep 13, 2024
ef505c4
Merge remote-tracking branch 'upstream/main' into task/olm-8537-rbac-…
paul-tavares Sep 16, 2024
07c7cec
Moved rbac CY test file to its own folder
paul-tavares Sep 16, 2024
e2f8baf
Add reusuable methods for Role page and spaces
paul-tavares Sep 17, 2024
6edb3ed
Test file (CY) for RBAC with space awareness
paul-tavares Sep 17, 2024
a595479
code stub for new `closeKibanaBrowserSecurityToastIfNecessary()`
paul-tavares Sep 17, 2024
f679b57
Merge remote-tracking branch 'upstream/main' into task/olm-8537-rbac-…
paul-tavares Sep 17, 2024
dede372
Close kibana browser req. toast when pages load
paul-tavares Sep 17, 2024
53ea5fa
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Sep 17, 2024
0baf5d9
DEV: Test code to report count os spaces
paul-tavares Sep 17, 2024
befe460
Merge remote-tracking branch 'origin/task/olm-8537-rbac-for-multiple-…
paul-tavares Sep 17, 2024
8d874a8
Remove dev code + add `maxSpaces` server config to test file
paul-tavares Sep 17, 2024
fb0c0bd
Add `data-test-subj` to several elements of the Security Role kibana …
paul-tavares Sep 18, 2024
724e085
Add additional test to validate tooltip is not displayed
paul-tavares Sep 18, 2024
995cc8a
additional test subjects for accordions in the kibana privileges form
paul-tavares Sep 18, 2024
52e90cf
Refactor role page selectors
paul-tavares Sep 18, 2024
2c07483
Update test tags and skip serverless flavors
paul-tavares Sep 18, 2024
10aa2cc
Merge remote-tracking branch 'upstream/main' into task/olm-8537-rbac-…
paul-tavares Sep 18, 2024
af63994
Update serverless FTR config to include options that enable role mana…
paul-tavares Sep 18, 2024
720353b
Update test and remove unnecessary server settings + set PLIs to comp…
paul-tavares Sep 18, 2024
1895340
Merge branch 'main' into task/olm-8537-rbac-for-multiple-spaces
paul-tavares Sep 19, 2024
d8d91d0
[CI] Auto-commit changed files from 'yarn openapi:bundle'
kibanamachine Sep 19, 2024
d7d2d21
Merge branch 'main' into task/olm-8537-rbac-for-multiple-spaces
paul-tavares Sep 19, 2024
9b6cdaa
Update failing test to use new css classname in determining if table …
paul-tavares Sep 19, 2024
51455db
Merge remote-tracking branch 'origin/task/olm-8537-rbac-for-multiple-…
paul-tavares Sep 19, 2024
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]> = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it wouldn't be more readable if you iterate over securitySubFeaturesList instead of wrapping each subFeature in enableSpaceAwarenessIfNeeded. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess. It accomplishes the same thing, but I'm open to changing it.

what do you find about this implementation that makes it less readable?

I did try to make the function names clear as to what they do and also opted to do it this way only because it gives us some flexibility if we ever have a case where a specific feature privilege does require it to be space agonistic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more about doing it in one place insterad of passing to all separately:

  const securitySubFeaturesListWithSpaceAwarnessIfNeeded = securitySubFeaturesList.map(
    ([id, config]) => [id, enableSpaceAwarenessIfNeeded(config)];
   );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah... Not sure if its better - I don't like that additional iteration by using .map() and recreating the entire structure again.

I'm going to leave it as is for now.

[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}` : ''}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always opt against having generators for test-ids because it's almost impossible to find the element from the web console in the codebase. What do you say if we hardcode the test-ids?

Copy link
Contributor Author

@paul-tavares paul-tavares Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually of the opposite opinion. Having static test ids in some cases makes it harder to find the elements you are after, especially when a component is reused multiple times on the page - as is the case here. Static only make sense (to me) when a component is not reused on a page (is only ever rendered once)

I rather keep it as is, unless you have a better way to select elements on the page in a more efficient way

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever I want to debug functionality, I get the data-test-id from devconsole. If it was hardcoded I could just use Ctrl+F and search for it, with the prefixes generators etc it's almost impossible - and have to look for chunks and later confirm if I found a correct prefix.

However, I don't insist, just sharing personal opinion :P It can stay this way 👍 Thanks!

Copy link
Contributor Author

@paul-tavares paul-tavares Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yeah, I do think we differ in the way we approach test development. If I'm working on e2e tests, I do in fact use the browser's developer tools to grab the data-test-subj's for elements I need to access. And that is what these changes are doing to the role forms. And in this case, because those forms are used multiple times on the page, using a static data-test-subj would negate the purpose as to why I made them - to facilitate and more predictably select elements in DOM.

I will leave it as is since you are ok with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, all good 👍 thanks for the consideration :)

},
[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