mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cloud Posture] include CSP plugin in siem feature (#134564)
This commit is contained in:
parent
071c878717
commit
b3da054084
12 changed files with 180 additions and 20 deletions
|
@ -17,6 +17,7 @@ import type { PageUrlParams } from './rules_container';
|
|||
import * as TEST_SUBJECTS from './test_subjects';
|
||||
import { useCisKubernetesIntegration } from '../../common/api/use_cis_kubernetes_integration';
|
||||
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
|
||||
jest.mock('./use_csp_integration', () => ({
|
||||
useCspIntegrationInfo: jest.fn(),
|
||||
|
@ -31,9 +32,20 @@ const queryClient = new QueryClient({
|
|||
|
||||
const getTestComponent =
|
||||
(params: PageUrlParams): React.FC =>
|
||||
() =>
|
||||
(
|
||||
<TestProvider>
|
||||
() => {
|
||||
const coreStart = coreMock.createStart();
|
||||
const core = {
|
||||
...coreStart,
|
||||
application: {
|
||||
...coreStart.application,
|
||||
capabilities: {
|
||||
...coreStart.application.capabilities,
|
||||
siem: { crud: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
return (
|
||||
<TestProvider core={core}>
|
||||
<Rules
|
||||
{...({
|
||||
match: { params },
|
||||
|
@ -41,11 +53,13 @@ const getTestComponent =
|
|||
/>
|
||||
</TestProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('<Rules />', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
jest.clearAllMocks();
|
||||
|
||||
(useCisKubernetesIntegration as jest.Mock).mockImplementation(() => ({
|
||||
data: { item: { status: 'installed' } },
|
||||
}));
|
||||
|
|
|
@ -14,6 +14,7 @@ import * as TEST_SUBJECTS from './test_subjects';
|
|||
import { Chance } from 'chance';
|
||||
import { TestProvider } from '../../test/test_provider';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
|
@ -34,9 +35,21 @@ const queryClient = new QueryClient({
|
|||
});
|
||||
|
||||
const getWrapper =
|
||||
(): React.FC =>
|
||||
({ children }) =>
|
||||
<TestProvider>{children}</TestProvider>;
|
||||
({ canUpdate = true }: { canUpdate: boolean } = { canUpdate: true }): React.FC =>
|
||||
({ children }) => {
|
||||
const coreStart = coreMock.createStart();
|
||||
const core = {
|
||||
...coreStart,
|
||||
application: {
|
||||
...coreStart.application,
|
||||
capabilities: {
|
||||
...coreStart.application.capabilities,
|
||||
siem: { crud: canUpdate },
|
||||
},
|
||||
},
|
||||
};
|
||||
return <TestProvider core={core}>{children}</TestProvider>;
|
||||
};
|
||||
|
||||
const getRuleMock = ({
|
||||
packagePolicyId = chance.guid(),
|
||||
|
@ -451,4 +464,79 @@ describe('<RulesContainer />', () => {
|
|||
savedObjectRules: [getSavedObjectRuleEnable(rule, !rule.attributes.enabled)],
|
||||
});
|
||||
});
|
||||
|
||||
it("disables rule toggling in table for users without 'crud' ui-capability", async () => {
|
||||
const Wrapper = getWrapper({ canUpdate: false });
|
||||
const rule1 = getRuleMock({ enabled: true });
|
||||
|
||||
(useFindCspRules as jest.Mock).mockReturnValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
total: 1,
|
||||
savedObjects: [rule1],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<Wrapper>
|
||||
<RulesContainer />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId(TEST_SUBJECTS.getCspRulesTableItemSwitchTestId(rule1.id))
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
it("disables bulk rule toggling in table for users without 'crud' ui-capability", async () => {
|
||||
const Wrapper = getWrapper({ canUpdate: false });
|
||||
const rule1 = getRuleMock({ enabled: true });
|
||||
|
||||
(useFindCspRules as jest.Mock).mockReturnValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
total: 1,
|
||||
savedObjects: [rule1],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<Wrapper>
|
||||
<RulesContainer />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId(TEST_SUBJECTS.CSP_RULES_TABLE_BULK_MENU_BUTTON));
|
||||
|
||||
expect(screen.getByTestId(TEST_SUBJECTS.CSP_RULES_TABLE_BULK_ENABLE_BUTTON)).toBeDisabled();
|
||||
expect(screen.getByTestId(TEST_SUBJECTS.CSP_RULES_TABLE_BULK_DISABLE_BUTTON)).toBeDisabled();
|
||||
});
|
||||
|
||||
it("disables rule toggling in flyout for users without 'crud' ui-capability", async () => {
|
||||
const Wrapper = getWrapper({ canUpdate: false });
|
||||
const rule1 = getRuleMock({ enabled: true });
|
||||
|
||||
(useFindCspRules as jest.Mock).mockReturnValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
total: 1,
|
||||
savedObjects: [rule1],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<Wrapper>
|
||||
<RulesContainer />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
const rowId = TEST_SUBJECTS.getCspRulesTableRowItemTestId(rule1.id);
|
||||
const switchId = TEST_SUBJECTS.getCspRulesTableItemSwitchTestId(rule1.id);
|
||||
|
||||
fireEvent.click(screen.getByTestId(rowId)); // open flyout
|
||||
|
||||
const flyout = screen.getByTestId(TEST_SUBJECTS.CSP_RULES_FLYOUT_CONTAINER);
|
||||
|
||||
expect(within(flyout).getByTestId(switchId)).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
import * as TEST_SUBJECTS from './test_subjects';
|
||||
import { RuleFlyout } from './rules_flyout';
|
||||
import { DATA_UPDATE_INFO } from './translations';
|
||||
import { useKibana } from '../../common/hooks/use_kibana';
|
||||
|
||||
interface RulesPageData {
|
||||
rules_page: RuleSavedObject[];
|
||||
|
@ -93,6 +94,7 @@ const MAX_ITEMS_PER_PAGE = 10000;
|
|||
export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>;
|
||||
|
||||
export const RulesContainer = () => {
|
||||
const canUpdate = !!useKibana().services.application.capabilities.siem.crud;
|
||||
const params = useParams<PageUrlParams>();
|
||||
const tableRef = useRef<EuiBasicTable>(null);
|
||||
const [changedRules, setChangedRules] = useState<Map<string, RuleSavedObject>>(new Map());
|
||||
|
@ -192,6 +194,7 @@ export const RulesContainer = () => {
|
|||
totalRulesCount={rulesPageData.all_rules.length}
|
||||
isSearching={status === 'loading'}
|
||||
lastModified={rulesPageData.lastModified}
|
||||
canUpdate={canUpdate}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<RulesTable
|
||||
|
@ -212,6 +215,7 @@ export const RulesContainer = () => {
|
|||
}
|
||||
setSelectedRuleId={setSelectedRuleId}
|
||||
selectedRuleId={selectedRuleId}
|
||||
canUpdate={canUpdate}
|
||||
/>
|
||||
</EuiPanel>
|
||||
{hasChanges && (
|
||||
|
@ -222,6 +226,7 @@ export const RulesContainer = () => {
|
|||
rule={changedRules.get(selectedRuleId) || rulesPageData.rules_map.get(selectedRuleId)!}
|
||||
onClose={() => setSelectedRuleId(null)}
|
||||
toggleRule={toggleRule}
|
||||
canUpdate={canUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -28,6 +28,7 @@ interface RuleFlyoutProps {
|
|||
onClose(): void;
|
||||
toggleRule(rule: RuleSavedObject): void;
|
||||
rule: RuleSavedObject;
|
||||
canUpdate: boolean;
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
|
@ -37,7 +38,7 @@ const tabs = [
|
|||
|
||||
type RuleTab = typeof tabs[number]['id'];
|
||||
|
||||
export const RuleFlyout = ({ onClose, rule, toggleRule }: RuleFlyoutProps) => {
|
||||
export const RuleFlyout = ({ onClose, rule, toggleRule, canUpdate }: RuleFlyoutProps) => {
|
||||
const [tab, setTab] = useState<RuleTab>('overview');
|
||||
|
||||
return (
|
||||
|
@ -65,7 +66,9 @@ export const RuleFlyout = ({ onClose, rule, toggleRule }: RuleFlyoutProps) => {
|
|||
</EuiTabs>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
{tab === 'overview' && <RuleOverviewTab rule={rule} toggleRule={() => toggleRule(rule)} />}
|
||||
{tab === 'overview' && (
|
||||
<RuleOverviewTab rule={rule} toggleRule={() => toggleRule(rule)} canUpdate={canUpdate} />
|
||||
)}
|
||||
{tab === 'remediation' && (
|
||||
<EuiDescriptionList
|
||||
compressed={false}
|
||||
|
@ -77,11 +80,20 @@ export const RuleFlyout = ({ onClose, rule, toggleRule }: RuleFlyoutProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const RuleOverviewTab = ({ rule, toggleRule }: { rule: RuleSavedObject; toggleRule(): void }) => (
|
||||
const RuleOverviewTab = ({
|
||||
rule,
|
||||
toggleRule,
|
||||
canUpdate,
|
||||
}: {
|
||||
rule: RuleSavedObject;
|
||||
toggleRule(): void;
|
||||
canUpdate: RuleFlyoutProps['canUpdate'];
|
||||
}) => (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<span>
|
||||
<EuiSwitch
|
||||
disabled={!canUpdate}
|
||||
label={TEXT.ACTIVATED}
|
||||
checked={rule.attributes.enabled}
|
||||
onChange={toggleRule}
|
||||
|
|
|
@ -31,6 +31,7 @@ type RulesTableProps = Pick<
|
|||
selectedRuleId: string | null;
|
||||
// ForwardRef makes this ref not available in parent callbacks
|
||||
tableRef: React.RefObject<EuiBasicTable<RuleSavedObject>>;
|
||||
canUpdate: boolean;
|
||||
};
|
||||
|
||||
export const RulesTable = ({
|
||||
|
@ -46,11 +47,12 @@ export const RulesTable = ({
|
|||
loading,
|
||||
error,
|
||||
selectedRuleId,
|
||||
canUpdate,
|
||||
}: RulesTableProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const columns = useMemo(
|
||||
() => getColumns({ toggleRule, setSelectedRuleId }),
|
||||
[setSelectedRuleId, toggleRule]
|
||||
() => getColumns({ toggleRule, setSelectedRuleId, canUpdate }),
|
||||
[setSelectedRuleId, toggleRule, canUpdate]
|
||||
);
|
||||
|
||||
const euiPagination: EuiBasicTableProps<RuleSavedObject>['pagination'] = {
|
||||
|
@ -99,13 +101,14 @@ export const RulesTable = ({
|
|||
);
|
||||
};
|
||||
|
||||
interface GetColumnProps extends Pick<RulesTableProps, 'setSelectedRuleId'> {
|
||||
interface GetColumnProps extends Pick<RulesTableProps, 'setSelectedRuleId' | 'canUpdate'> {
|
||||
toggleRule: (rule: RuleSavedObject) => void;
|
||||
}
|
||||
|
||||
const getColumns = ({
|
||||
toggleRule,
|
||||
setSelectedRuleId,
|
||||
canUpdate,
|
||||
}: GetColumnProps): Array<EuiTableFieldDataColumnType<RuleSavedObject>> => [
|
||||
{
|
||||
field: 'attributes.metadata.name',
|
||||
|
@ -142,6 +145,7 @@ const getColumns = ({
|
|||
name: TEXT.ENABLED,
|
||||
render: (enabled, rule) => (
|
||||
<EuiSwitch
|
||||
disabled={!canUpdate}
|
||||
showLabel={false}
|
||||
label={enabled ? TEXT.DISABLE : TEXT.ENABLE}
|
||||
checked={enabled}
|
||||
|
|
|
@ -33,6 +33,7 @@ interface RulesTableToolbarProps {
|
|||
searchValue: string;
|
||||
isSearching: boolean;
|
||||
lastModified: string | null;
|
||||
canUpdate: boolean;
|
||||
}
|
||||
|
||||
interface CounterProps {
|
||||
|
@ -65,6 +66,7 @@ export const RulesTableHeader = ({
|
|||
searchValue,
|
||||
isSearching,
|
||||
lastModified,
|
||||
canUpdate,
|
||||
}: RulesTableToolbarProps) => (
|
||||
<div>
|
||||
{lastModified && <LastModificationLabel lastModified={lastModified} />}
|
||||
|
@ -77,6 +79,7 @@ export const RulesTableHeader = ({
|
|||
select={selectAll}
|
||||
/>
|
||||
<BulkMenu
|
||||
canUpdate={canUpdate}
|
||||
bulkEnable={bulkEnable}
|
||||
bulkDisable={bulkDisable}
|
||||
selectedRulesCount={selectedRulesCount}
|
||||
|
@ -113,20 +116,24 @@ const BulkMenu = ({
|
|||
bulkEnable,
|
||||
bulkDisable,
|
||||
selectedRulesCount,
|
||||
}: Pick<RulesTableToolbarProps, 'bulkDisable' | 'bulkEnable' | 'selectedRulesCount'>) => (
|
||||
canUpdate,
|
||||
}: Pick<
|
||||
RulesTableToolbarProps,
|
||||
'bulkDisable' | 'bulkEnable' | 'selectedRulesCount' | 'canUpdate'
|
||||
>) => (
|
||||
<EuiFlexItem grow={false}>
|
||||
<RulesBulkActionsMenu
|
||||
items={[
|
||||
{
|
||||
icon: 'eye',
|
||||
disabled: !selectedRulesCount,
|
||||
disabled: !selectedRulesCount || !canUpdate,
|
||||
children: <ActivateRulesMenuItemText count={selectedRulesCount} />,
|
||||
'data-test-subj': TEST_SUBJECTS.CSP_RULES_TABLE_BULK_ENABLE_BUTTON,
|
||||
onClick: bulkEnable,
|
||||
},
|
||||
{
|
||||
icon: 'eyeClosed',
|
||||
disabled: !selectedRulesCount,
|
||||
disabled: !selectedRulesCount || !canUpdate,
|
||||
children: <DeactivateRulesMenuItemText count={selectedRulesCount} />,
|
||||
'data-test-subj': TEST_SUBJECTS.CSP_RULES_TABLE_BULK_DISABLE_BUTTON,
|
||||
onClick: bulkDisable,
|
||||
|
|
|
@ -201,6 +201,9 @@ export const defineGetBenchmarksRoute = (router: CspRouter, cspContext: CspAppCo
|
|||
{
|
||||
path: BENCHMARKS_ROUTE_PATH,
|
||||
validate: { query: benchmarksInputSchema },
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-read'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
if (!(await context.fleet).authz.fleet.all) {
|
||||
|
|
|
@ -41,6 +41,9 @@ export const defineGetComplianceDashboardRoute = (
|
|||
{
|
||||
path: STATS_ROUTE_PATH,
|
||||
validate: false,
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-read'],
|
||||
},
|
||||
},
|
||||
async (context, _, response) => {
|
||||
try {
|
||||
|
|
|
@ -129,6 +129,9 @@ export const defineUpdateRulesConfigRoute = (router: CspRouter, cspContext: CspA
|
|||
{
|
||||
path: UPDATE_RULES_CONFIG_ROUTE_PATH,
|
||||
validate: { body: configurationUpdateInputSchema },
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-all'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
if (!(await context.fleet).authz.fleet.all) {
|
||||
|
|
|
@ -23,6 +23,9 @@ export const defineEsPitRoute = (router: CspRouter, cspContext: CspAppContext):
|
|||
{
|
||||
path: ES_PIT_ROUTE_PATH,
|
||||
validate: { query: esPitInputSchema },
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-read'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
if (!(await context.fleet).authz.fleet.all) {
|
||||
|
|
|
@ -36,6 +36,9 @@ export const defineGetCspSetupStatusRoute = (router: CspRouter, cspContext: CspA
|
|||
{
|
||||
path: INFO_ROUTE_PATH,
|
||||
validate: false,
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-read'],
|
||||
},
|
||||
},
|
||||
async (context, _, response) => {
|
||||
try {
|
||||
|
|
|
@ -100,6 +100,11 @@ export const getAlertsSubFeature = (ruleTypes: string[]): SubFeatureConfig => ({
|
|||
],
|
||||
});
|
||||
|
||||
// Same as the plugin id defined by Cloud Security Posture
|
||||
const CLOUD_POSTURE_APP_ID = 'csp';
|
||||
// Same as the saved-object type for rules defined by Cloud Security Posture
|
||||
const CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE = 'csp_rule';
|
||||
|
||||
export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): KibanaFeatureConfig => ({
|
||||
id: SERVER_APP_ID,
|
||||
name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle', {
|
||||
|
@ -107,7 +112,7 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
|
|||
}),
|
||||
order: 1100,
|
||||
category: DEFAULT_APP_CATEGORIES.security,
|
||||
app: [APP_ID, 'kibana'],
|
||||
app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
management: {
|
||||
insightsAndAlerting: ['triggersActions'],
|
||||
|
@ -116,9 +121,17 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
|
|||
subFeatures: [],
|
||||
privileges: {
|
||||
all: {
|
||||
app: [APP_ID, 'kibana'],
|
||||
app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
api: [APP_ID, 'lists-all', 'lists-read', 'lists-summary', 'rac'],
|
||||
api: [
|
||||
APP_ID,
|
||||
'lists-all',
|
||||
'lists-read',
|
||||
'lists-summary',
|
||||
'rac',
|
||||
'cloud-security-posture-all',
|
||||
'cloud-security-posture-read',
|
||||
],
|
||||
savedObject: {
|
||||
all: [
|
||||
'alert',
|
||||
|
@ -126,6 +139,7 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
|
|||
'exception-list-agnostic',
|
||||
DATA_VIEW_SAVED_OBJECT_TYPE,
|
||||
...savedObjectTypes,
|
||||
CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE,
|
||||
],
|
||||
read: [],
|
||||
},
|
||||
|
@ -143,9 +157,9 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
|
|||
ui: ['show', 'crud'],
|
||||
},
|
||||
read: {
|
||||
app: [APP_ID, 'kibana'],
|
||||
app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
api: [APP_ID, 'lists-read', 'rac'],
|
||||
api: [APP_ID, 'lists-read', 'rac', 'cloud-security-posture-read'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [
|
||||
|
@ -153,6 +167,7 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
|
|||
'exception-list-agnostic',
|
||||
DATA_VIEW_SAVED_OBJECT_TYPE,
|
||||
...savedObjectTypes,
|
||||
CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE,
|
||||
],
|
||||
},
|
||||
alerting: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue