Skip to content

Commit

Permalink
[Security Solutions] Add PLI authorisation for Advanced Insights (Ent…
Browse files Browse the repository at this point in the history
…ity Risk) (#161190)

## Summary

Add PLI authorization checks for Entity Analytics features.
*This PR only restricts access to the features* but doesn't implement
PLG/Upselling. It will be added later when we have defined the UX for
it.

The `advancedInsights` PLI was already configured, so I only had to add
extra checks to make sure users can't see the Risk score on other
components.
Updated components:
* "All hosts" table on the Hosts page
* "All users" table on the Users page
* Host overview on the Host details page and Host details flyout
* User overview on the User details page and User details flyout
* Alerts flyout
* Remove sample Upselling components config

### Not included
* Upselling/PLG
* I left empty tabs/pages where the Upselling component will be added

### How to test it?
#### ESS
* Run ESS with a basic license
* Run ESS with a platinum

#### Serverless
* Run Serverless with security essentials (serverless.security.yml)
```
xpack.serverless.security.productTypes:
  [
    { product_line: 'security', product_tier: 'essentials' }
  ]
```
* Run Serverless with security complete
(kibana/config/serverless.security.yml)
```
xpack.serverless.security.productTypes:
  [
    { product_line: 'security', product_tier: 'complete' },
  ]
 
 ```


https://github.com/elastic/kibana/assets/1490444/1ab84134-bee1-497c-9b41-a9ec398bd921

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
machadoum and kibanamachine authored Jul 26, 2023
1 parent c0cb613 commit a074c06
Show file tree
Hide file tree
Showing 39 changed files with 273 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jest.mock('../../../lib/kibana', () => ({
}),
}));

jest.mock('../../../../helper_hooks', () => ({
useHasSecurityCapability: () => true,
}));

jest.mock('../table/field_name_cell');

const RISK_SCORE_DATA_ROWS = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { RiskSummary } from './risk_summary';
import { EnrichmentSummary } from './enrichment_summary';
import type { HostRisk, UserRisk } from '../../../../explore/containers/risk_score';
import { RiskScoreEntity } from '../../../../../common/search_strategy';
import { useHasSecurityCapability } from '../../../../helper_hooks';

const UppercaseEuiTitle = styled(EuiTitle)`
text-transform: uppercase;
Expand Down Expand Up @@ -151,6 +152,12 @@ const ThreatSummaryViewComponent: React.FC<{
(eventDetail) => eventDetail?.field === 'user.risk.calculated_level'
)?.values?.[0] as RiskSeverity | undefined;

const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');

if (!hasEntityAnalyticsCapability && enrichments.length === 0) {
return null;
}

return (
<>
<EuiHorizontalRule />
Expand All @@ -161,21 +168,25 @@ const ThreatSummaryViewComponent: React.FC<{
<EuiSpacer size="m" />

<EuiFlexGroup direction="column" gutterSize="m" style={{ flexGrow: 0 }}>
<EuiFlexItem grow={false}>
<RiskSummary
riskEntity={RiskScoreEntity.host}
risk={hostRisk}
originalRisk={originalHostRisk}
/>
</EuiFlexItem>

<EuiFlexItem grow={false}>
<RiskSummary
riskEntity={RiskScoreEntity.user}
risk={userRisk}
originalRisk={originalUserRisk}
/>
</EuiFlexItem>
{hasEntityAnalyticsCapability && (
<>
<EuiFlexItem grow={false}>
<RiskSummary
riskEntity={RiskScoreEntity.host}
risk={hostRisk}
originalRisk={originalHostRisk}
/>
</EuiFlexItem>

<EuiFlexItem grow={false}>
<RiskSummary
riskEntity={RiskScoreEntity.user}
risk={userRisk}
originalRisk={originalUserRisk}
/>
</EuiFlexItem>
</>
)}

<EnrichmentSummary
browserFields={browserFields}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const EventDetailsComponent: React.FC<Props> = ({

const enrichmentCount = allEnrichments.length;

const { hostRisk, userRisk, isLicenseValid } = useRiskScoreData(data);
const { hostRisk, userRisk, isAuthorized } = useRiskScoreData(data);

const renderer = useMemo(
() =>
Expand All @@ -212,9 +212,9 @@ const EventDetailsComponent: React.FC<Props> = ({

const showThreatSummary = useMemo(() => {
const hasEnrichments = enrichmentCount > 0;
const hasRiskInfoWithLicense = isLicenseValid && (hostRisk || userRisk);
const hasRiskInfoWithLicense = isAuthorized && (hostRisk || userRisk);
return hasEnrichments || hasRiskInfoWithLicense;
}, [enrichmentCount, hostRisk, isLicenseValid, userRisk]);
}, [enrichmentCount, hostRisk, isAuthorized, userRisk]);
const endpointResponseActionsEnabled = useIsExperimentalFeatureEnabled(
'endpointResponseActionsEnabled'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const defaultResult = {
data: [],
inspect: {},
isInspected: false,
isLicenseValid: true,
isAuthorized: true,
isModuleEnabled: true,
refetch: () => {},
totalCount: 0,
Expand Down Expand Up @@ -55,7 +55,7 @@ describe('useRiskScoreData', () => {
expect(result.current).toEqual({
hostRisk: defaultRisk,
userRisk: defaultRisk,
isLicenseValid: true,
isAuthorized: true,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
const {
data: hostRiskData,
loading: hostRiskLoading,
isLicenseValid: isHostLicenseValid,
isAuthorized: isHostRiskScoreAuthorized,
isModuleEnabled: isHostRiskModuleEnabled,
} = useRiskScore({
filterQuery: hostNameFilterQuery,
Expand All @@ -57,7 +57,7 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
const {
data: userRiskData,
loading: userRiskLoading,
isLicenseValid: isUserLicenseValid,
isAuthorized: isUserRiskScoreAuthorized,
isModuleEnabled: isUserRiskModuleEnabled,
} = useRiskScore({
filterQuery: userNameFilterQuery,
Expand All @@ -75,5 +75,9 @@ export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => {
[userRiskLoading, isUserRiskModuleEnabled, userRiskData]
);

return { userRisk, hostRisk, isLicenseValid: isHostLicenseValid && isUserLicenseValid };
return {
userRisk,
hostRisk,
isAuthorized: isHostRiskScoreAuthorized && isUserRiskScoreAuthorized,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
DEFAULT_RULES_TABLE_REFRESH_SETTING,
DEFAULT_RULE_REFRESH_INTERVAL_ON,
DEFAULT_RULE_REFRESH_INTERVAL_VALUE,
SERVER_APP_ID,
} from '../../../../common/constants';
import type { StartServices } from '../../../types';
import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage';
Expand Down Expand Up @@ -178,6 +179,16 @@ export const createStartServicesMock = (
})),
},
},
application: {
...core.application,
capabilities: {
...core.application.capabilities,
[SERVER_APP_ID]: {
crud: true,
read: true,
},
},
},
security,
storage,
fleet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => {
inspect: null,
refetch: () => {},
isModuleEnabled: true,
isLicenseValid: true,
isAuthorized: true,
loading: false,
};
const HostPanelWithDefaultProps = (propOverrides: Partial<HostPanelProps>) => (
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => {
it('should not show risk if the license is not valid', () => {
mockUseRiskScore.mockReturnValue({
...defaultRiskReturnValues,
isLicenseValid: false,
isAuthorized: false,
data: null,
});
const { queryByTestId } = render(<HostPanelWithDefaultProps />);
Expand All @@ -119,7 +119,7 @@ describe('AlertDetailsPage - SummaryTab - HostPanel', () => {

mockUseRiskScore.mockReturnValue({
...defaultRiskReturnValues,
isLicenseValid: true,
isAuthorized: true,
data: [
{
host: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const HostPanel = React.memo(
);
}, [browserFields, data, id]);

const { data: hostRisk, isLicenseValid: isRiskLicenseValid } = useRiskScore({
const { data: hostRisk, isAuthorized: isRiskScoreAuthorized } = useRiskScore({
riskEntity: RiskScoreEntity.host,
skip: hostName == null,
});
Expand Down Expand Up @@ -149,7 +149,7 @@ export const HostPanel = React.memo(
)}
</EuiFlexGroup>
<EuiSpacer size="l" />
{isRiskLicenseValid && (
{isRiskScoreAuthorized && (
<>
<EuiFlexGroup data-test-subj="host-panel-risk">
{hostRiskScore && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const UserPanel = React.memo(
({ data, selectedPatterns, openUserDetailsPanel }: UserPanelProps) => {
const userName = useMemo(() => getTimelineEventData('user.name', data), [data]);

const { data: userRisk, isLicenseValid: isRiskLicenseValid } = useRiskScore({
const { data: userRisk, isAuthorized: isRiskScoreAuthorized } = useRiskScore({
riskEntity: RiskScoreEntity.user,
skip: userName == null,
});
Expand Down Expand Up @@ -114,7 +114,7 @@ export const UserPanel = React.memo(
</UserPanelSection>
</EuiFlexGroup>
<EuiSpacer size="l" />
{isRiskLicenseValid && (
{isRiskScoreAuthorized && (
<>
<EuiFlexGroup data-test-subj="user-panel-risk">
{userRiskScore && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('AlertDetailsPage - SummaryTab - UserPanel', () => {
inspect: null,
refetch: () => {},
isModuleEnabled: true,
isLicenseValid: true,
isAuthorized: true,
loading: false,
};
const UserPanelWithDefaultProps = (propOverrides: Partial<UserPanelProps>) => (
Expand Down Expand Up @@ -64,7 +64,7 @@ describe('AlertDetailsPage - SummaryTab - UserPanel', () => {
it('should not show risk if the license is not valid', () => {
mockUseRiskScore.mockReturnValue({
...defaultRiskReturnValues,
isLicenseValid: false,
isAuthorized: false,
data: null,
});
const { queryByTestId } = render(<UserPanelWithDefaultProps />);
Expand All @@ -77,7 +77,7 @@ describe('AlertDetailsPage - SummaryTab - UserPanel', () => {

mockUseRiskScore.mockReturnValue({
...defaultRiskReturnValues,
isLicenseValid: true,
isAuthorized: true,
data: [
{
user: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import type { UsersComponentsQueryProps } from '../../../users/pages/navigation/
import type { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types';
import { useDashboardHref } from '../../../../common/hooks/use_dashboard_href';
import { RiskScoresNoDataDetected } from '../risk_score_onboarding/risk_score_no_data_detected';
import { useUpsellingComponent } from '../../../../common/hooks/use_upselling';

const StyledEuiFlexGroup = styled(EuiFlexGroup)`
margin-top: ${({ theme }) => theme.eui.euiSizeL};
Expand All @@ -49,7 +48,6 @@ const RiskDetailsTabBodyComponent: React.FC<
riskEntity: RiskScoreEntity;
}
> = ({ entityName, startDate, endDate, setQuery, deleteQuery, riskEntity }) => {
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
const queryId = useMemo(
() =>
riskEntity === RiskScoreEntity.host
Expand Down Expand Up @@ -84,13 +82,14 @@ const RiskDetailsTabBodyComponent: React.FC<
[entityName, riskEntity]
);

const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled } = useRiskScore({
filterQuery,
onlyLatest: false,
riskEntity,
skip: !overTimeToggleStatus && !contributorsToggleStatus,
timerange,
});
const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled, isAuthorized } =
useRiskScore({
filterQuery,
onlyLatest: false,
riskEntity,
skip: !overTimeToggleStatus && !contributorsToggleStatus,
timerange,
});

const rules = useMemo(() => {
const lastRiskItem = data && data.length > 0 ? data[data.length - 1] : null;
Expand Down Expand Up @@ -130,8 +129,8 @@ const RiskDetailsTabBodyComponent: React.FC<
isDeprecated: isDeprecated && !loading,
};

if (RiskScoreUpsell) {
return <RiskScoreUpsell />;
if (!isAuthorized) {
return <>{'TODO: Add RiskScore Upsell'}</>;
}

if (status.isDisabled || status.isDeprecated) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
const defaultFeatureStatus = {
isLoading: false,
isDeprecated: false,
isLicenseValid: true,
isAuthorized: true,
isEnabled: true,
refetch: () => {},
};
const defaultRisk = {
data: undefined,
inspect: {},
isInspected: false,
isLicenseValid: true,
isAuthorized: true,
isModuleEnabled: true,
isDeprecated: false,
totalCount: 0,
Expand Down Expand Up @@ -72,7 +72,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
test('does not search if license is not valid', () => {
mockUseRiskScoreFeatureStatus.mockReturnValue({
...defaultFeatureStatus,
isLicenseValid: false,
isAuthorized: false,
});
const { result } = renderHook(() => useRiskScore({ riskEntity }), {
wrapper: TestProviders,
Expand All @@ -81,7 +81,7 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
expect(result.current).toEqual({
loading: false,
...defaultRisk,
isLicenseValid: false,
isAuthorized: false,
refetch: result.current.refetch,
});
});
Expand Down
Loading

0 comments on commit a074c06

Please sign in to comment.