[Security Solution] Allow only users with 'all' privileges to install and upgrade prebuilt rules (#161454)

Fixes: https://github.com/elastic/kibana/issues/161443

## Summary

### When user doesn't have write permission:
- Disables "Add Elastic rules" button and removes Rule Updates tab

![image](a173f18f-9b6b-4c9a-bf5f-207af13e24cb)

- Disables buttons to individually install rules, install selected rules
and install all rules

![image](4d24d440-17f4-4d1d-96fc-4eb07914cff0)

- Disables buttons to individually upgrade rules, upgrade selected rules
and upgrade all rules

![image](036236c1-dac0-42b8-87e5-0244d9ead281)

### `_perform` endpoints
- Returns 403 when installing all rules or specific rules

![image](adc20409-ff09-42e5-aa33-0f1ec0df46f6)

![image](d1faf778-d857-458e-afeb-7c573e7bf4d3)

- Returns 403 when upgrading all rules or specific rules

![image](b21ffaa7-416c-402a-a157-12735f28e689)

![image](b8dfecc6-4cfe-462c-9e9c-6344f59aa2d5)


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: Dmitrii <dmitrii.shevchenko@elastic.co>
This commit is contained in:
Juan Pablo Djeredjian 2023-07-10 16:35:17 +02:00 committed by GitHub
parent 4baeafe4e6
commit 31b28a0660
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 38 additions and 24 deletions

View file

@ -7,6 +7,7 @@
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react';
import { useUserData } from '../../../../../detections/components/user_info';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
import * as i18n from './translations';
@ -15,6 +16,8 @@ export const AddPrebuiltRulesHeaderButtons = () => {
state: { rules, selectedRules, loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { installAllRules, installSelectedRules },
} = useAddPrebuiltRulesTableContext();
const [{ loading: isUserDataLoading, canUserCRUD }] = useUserData();
const canUserEditRules = canUserCRUD && !isUserDataLoading;
const isRulesAvailableForInstall = rules.length > 0;
const numberOfSelectedRules = selectedRules.length ?? 0;
@ -29,7 +32,7 @@ export const AddPrebuiltRulesHeaderButtons = () => {
<EuiFlexItem grow={false}>
<EuiButton
onClick={installSelectedRules}
disabled={isRequestInProgress}
disabled={!canUserEditRules || isRequestInProgress}
data-test-subj="installSelectedRulesButton"
>
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
@ -43,7 +46,7 @@ export const AddPrebuiltRulesHeaderButtons = () => {
iconType="plusInCircle"
data-test-subj="installAllRulesButton"
onClick={installAllRules}
disabled={!isRulesAvailableForInstall || isRequestInProgress}
disabled={!canUserEditRules || !isRulesAvailableForInstall || isRequestInProgress}
>
{i18n.INSTALL_ALL}
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : undefined}

View file

@ -6,6 +6,7 @@
*/
import React, { useMemo } from 'react';
import { useUserData } from '../../../../detections/components/user_info';
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
import { usePrebuiltRulesStatus } from '../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_status';
import { useRuleManagementFilters } from '../../../rule_management/logic/use_rule_management_filters';
@ -21,26 +22,14 @@ export const RulesTableToolbar = React.memo(() => {
const { data: ruleManagementFilters } = useRuleManagementFilters();
const { data: prebuiltRulesStatus } = usePrebuiltRulesStatus();
const [{ loading, canUserCRUD }] = useUserData();
const installedTotal =
(ruleManagementFilters?.rules_summary.custom_count ?? 0) +
(ruleManagementFilters?.rules_summary.prebuilt_installed_count ?? 0);
const updateTotal = prebuiltRulesStatus?.num_prebuilt_rules_to_upgrade ?? 0;
const ruleUpdateTab = useMemo(
() => ({
[AllRulesTabs.updates]: {
id: AllRulesTabs.updates,
name: i18n.RULE_UPDATES_TAB,
disabled: false,
href: `/rules/${AllRulesTabs.updates}`,
isBeta: updateTotal > 0,
betaOptions: {
text: `${updateTotal}`,
},
},
}),
[updateTotal]
);
const shouldDisplayRuleUpdatesTab = !loading && canUserCRUD && updateTotal > 0;
const ruleTabs = useMemo(
() => ({
@ -64,9 +53,22 @@ export const RulesTableToolbar = React.memo(() => {
text: `${installedTotal}`,
},
},
...(updateTotal > 0 ? ruleUpdateTab : {}),
...(shouldDisplayRuleUpdatesTab
? {
[AllRulesTabs.updates]: {
id: AllRulesTabs.updates,
name: i18n.RULE_UPDATES_TAB,
disabled: false,
href: `/rules/${AllRulesTabs.updates}`,
isBeta: updateTotal > 0,
betaOptions: {
text: `${updateTotal}`,
},
},
}
: {}),
}),
[installedTotal, ruleUpdateTab, updateTotal]
[installedTotal, updateTotal, shouldDisplayRuleUpdatesTab]
);
return <TabNavigation navTabs={ruleTabs} />;

View file

@ -7,6 +7,7 @@
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react';
import { useUserData } from '../../../../../detections/components/user_info';
import * as i18n from './translations';
import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context';
@ -15,6 +16,8 @@ export const UpgradePrebuiltRulesTableButtons = () => {
state: { rules, selectedRules, loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { upgradeAllRules, upgradeSelectedRules },
} = useUpgradePrebuiltRulesTableContext();
const [{ loading: isUserDataLoading, canUserCRUD }] = useUserData();
const canUserEditRules = canUserCRUD && !isUserDataLoading;
const isRulesAvailableForUpgrade = rules.length > 0;
const numberOfSelectedRules = selectedRules.length ?? 0;
@ -29,7 +32,7 @@ export const UpgradePrebuiltRulesTableButtons = () => {
<EuiFlexItem grow={false}>
<EuiButton
onClick={upgradeSelectedRules}
disabled={isRequestInProgress}
disabled={!canUserEditRules || isRequestInProgress}
data-test-subj="upgradeSelectedRulesButton"
>
<>
@ -44,7 +47,7 @@ export const UpgradePrebuiltRulesTableButtons = () => {
fill
iconType="plusInCircle"
onClick={upgradeAllRules}
disabled={!isRulesAvailableForUpgrade || isRequestInProgress}
disabled={!canUserEditRules || !isRulesAvailableForUpgrade || isRequestInProgress}
data-test-subj="upgradeAllRulesButton"
>
{i18n.UPDATE_ALL}

View file

@ -107,7 +107,7 @@ const RulesPageComponent: React.FC = () => {
<SuperHeader>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
<EuiFlexItem grow={false}>
<AddElasticRulesButton />
<AddElasticRulesButton isDisabled={!canUserCRUD || loading} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip position="top" content={i18n.UPLOAD_VALUE_LISTS_TOOLTIP}>

View file

@ -17,12 +17,14 @@ import { usePrebuiltRulesStatus } from '../../../../detection_engine/rule_manage
interface AddElasticRulesButtonProps {
'data-test-subj'?: string;
fill?: boolean;
isDisabled: boolean;
showBadge?: boolean;
}
export const AddElasticRulesButton = ({
'data-test-subj': dataTestSubj = 'addElasticRulesButton',
fill,
isDisabled,
showBadge = true,
}: AddElasticRulesButtonProps) => {
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps();
@ -43,6 +45,7 @@ export const AddElasticRulesButton = ({
color={'primary'}
onClick={onClickLink}
data-test-subj={dataTestSubj}
isDisabled={isDisabled}
>
{i18n.ADD_ELASTIC_RULES}
{newRulesCount > 0 && showBadge && (

View file

@ -8,6 +8,7 @@
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { memo } from 'react';
import styled from 'styled-components';
import { useUserData } from '../../user_info';
import { AddElasticRulesButton } from './add_elastic_rules_button';
import * as i18n from './translations';
@ -18,6 +19,7 @@ const EmptyPrompt = styled(EuiEmptyPrompt)`
EmptyPrompt.displayName = 'EmptyPrompt';
const PrePackagedRulesPromptComponent = () => {
const [{ loading, canUserCRUD }] = useUserData();
return (
<EmptyPrompt
data-test-subj="rulesEmptyPrompt"
@ -27,6 +29,7 @@ const PrePackagedRulesPromptComponent = () => {
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<AddElasticRulesButton
isDisabled={!canUserCRUD || loading}
fill={true}
data-test-subj="add-elastc-rules-empty-empty-prompt-button"
showBadge={false}

View file

@ -35,7 +35,7 @@ export const performRuleInstallationRoute = (router: SecuritySolutionPluginRoute
body: buildRouteValidation(PerformRuleInstallationRequestBody),
},
options: {
tags: ['access:securitySolution'],
tags: ['access:securitySolution-all'],
},
},
async (context, request, response) => {

View file

@ -39,7 +39,7 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) =>
body: buildRouteValidation(PerformRuleUpgradeRequestBody),
},
options: {
tags: ['access:securitySolution'],
tags: ['access:securitySolution-all'],
},
},
async (context, request, response) => {