[Security Solution] Top-level Cases feature under the Security (#112980)

* add the new top cases feature in security

* fix api intyegration and cypress

* fix api integration

* fix cypress roles test

* missing api integration

* review Joe

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Xavier Mouligneau 2021-09-30 20:18:25 -04:00 committed by GitHub
parent 01e6ff3b53
commit 3b958e76aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 91 additions and 68 deletions

View file

@ -10,6 +10,7 @@ import { ENABLE_CASE_CONNECTOR } from '../../cases/common';
import { metadataTransformPattern } from './endpoint/constants';
export const APP_ID = 'securitySolution';
export const CASES_FEATURE_ID = 'securitySolutionCases';
export const SERVER_APP_ID = 'siem';
export const APP_NAME = 'Security';
export const APP_ICON = 'securityAnalyticsApp';

View file

@ -81,6 +81,7 @@ const secAll: Role = {
{
feature: {
siem: ['all'],
securitySolutionCases: ['all'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -110,7 +111,8 @@ const secReadCasesAll: Role = {
kibana: [
{
feature: {
siem: ['minimal_read', 'cases_all'],
siem: ['read'],
securitySolutionCases: ['all'],
actions: ['all'],
actionsSimulators: ['all'],
},

View file

@ -8,6 +8,7 @@ import { getDeepLinks, PREMIUM_DEEP_LINK_IDS } from '.';
import { AppDeepLink, Capabilities } from '../../../../../../src/core/public';
import { SecurityPageName } from '../types';
import { mockGlobalState } from '../../common/mock';
import { CASES_FEATURE_ID } from '../../../common/constants';
const findDeepLink = (id: string, deepLinks: AppDeepLink[]): AppDeepLink | null =>
deepLinks.reduce((deepLinkFound: AppDeepLink | null, deepLink) => {
@ -58,7 +59,7 @@ describe('deepLinks', () => {
it('should return case links for basic license with only read_cases capabilities', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
siem: { read_cases: true, crud_cases: false },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
} as unknown as Capabilities);
expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy();
@ -67,7 +68,7 @@ describe('deepLinks', () => {
it('should return case links with NO deepLinks for basic license with only read_cases capabilities', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
siem: { read_cases: true, crud_cases: false },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
} as unknown as Capabilities);
expect(findDeepLink(SecurityPageName.case, basicLinks)?.deepLinks?.length === 0).toBeTruthy();
});
@ -75,7 +76,7 @@ describe('deepLinks', () => {
it('should return case links with deepLinks for basic license with crud_cases capabilities', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
siem: { read_cases: true, crud_cases: true },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: true },
} as unknown as Capabilities);
expect(
@ -86,7 +87,7 @@ describe('deepLinks', () => {
it('should return NO case links for basic license with NO read_cases capabilities', () => {
const basicLicense = 'basic';
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
siem: { read_cases: false, crud_cases: false },
[CASES_FEATURE_ID]: { read_cases: false, crud_cases: false },
} as unknown as Capabilities);
expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeFalsy();

View file

@ -48,6 +48,7 @@ import {
TRUSTED_APPS_PATH,
EVENT_FILTERS_PATH,
UEBA_PATH,
CASES_FEATURE_ID,
HOST_ISOLATION_EXCEPTIONS_PATH,
} from '../../../common/constants';
import { ExperimentalFeatures } from '../../../common/experimental_features';
@ -362,7 +363,7 @@ export function getDeepLinks(
return false;
}
if (deepLink.id === SecurityPageName.case) {
return capabilities == null || capabilities.siem.read_cases === true;
return capabilities == null || capabilities[CASES_FEATURE_ID].read_cases === true;
}
if (deepLink.id === SecurityPageName.ueba) {
return enableExperimental.uebaEnabled;
@ -373,7 +374,7 @@ export function getDeepLinks(
if (
deepLink.id === SecurityPageName.case &&
capabilities != null &&
capabilities.siem.crud_cases === false
capabilities[CASES_FEATURE_ID].crud_cases === false
) {
return {
...deepLink,

View file

@ -12,7 +12,12 @@ import { i18n } from '@kbn/i18n';
import { camelCase, isArray, isObject } from 'lodash';
import { set } from '@elastic/safer-lodash-set';
import { APP_ID, DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants';
import {
APP_ID,
CASES_FEATURE_ID,
DEFAULT_DATE_FORMAT,
DEFAULT_DATE_FORMAT_TZ,
} from '../../../../common/constants';
import { errorToToaster, useStateToaster } from '../../components/toasters';
import { AuthenticatedUser } from '../../../../../security/common/model';
import { NavigateToAppOptions } from '../../../../../../../src/core/public';
@ -151,13 +156,9 @@ export const useGetUserCasesPermissions = () => {
const uiCapabilities = useKibana().services.application.capabilities;
useEffect(() => {
const capabilitiesCanUserCRUD: boolean =
typeof uiCapabilities.siem.crud_cases === 'boolean' ? uiCapabilities.siem.crud_cases : false;
const capabilitiesCanUserRead: boolean =
typeof uiCapabilities.siem.read_cases === 'boolean' ? uiCapabilities.siem.read_cases : false;
setCasesPermissions({
crud: capabilitiesCanUserCRUD,
read: capabilitiesCanUserRead,
crud: !!uiCapabilities[CASES_FEATURE_ID].crud_cases,
read: !!uiCapabilities[CASES_FEATURE_ID].read_cases,
});
}, [uiCapabilities]);

View file

@ -9,49 +9,48 @@ import { i18n } from '@kbn/i18n';
import { KibanaFeatureConfig, SubFeatureConfig } from '../../features/common';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
import { APP_ID, SERVER_APP_ID } from '../common/constants';
import { APP_ID, CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants';
import { savedObjectTypes } from './saved_objects';
const CASES_SUB_FEATURE: SubFeatureConfig = {
name: 'Cases',
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
id: 'cases_all',
includeIn: 'all',
name: 'All',
savedObject: {
all: [],
read: [],
},
// using variables with underscores here otherwise when we retrieve them from the kibana
// capabilities in a hook I get type errors regarding boolean | ReadOnly<{[x: string]: boolean}>
ui: ['crud_cases', 'read_cases'], // uiCapabilities.siem.crud_cases
export const getCasesKibanaFeature = (): KibanaFeatureConfig => ({
id: CASES_FEATURE_ID,
name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle', {
defaultMessage: 'Cases',
}),
order: 1100,
category: DEFAULT_APP_CATEGORIES.security,
app: [CASES_FEATURE_ID, 'kibana'],
catalogue: [APP_ID],
cases: [APP_ID],
privileges: {
all: {
app: [CASES_FEATURE_ID, 'kibana'],
catalogue: [APP_ID],
cases: {
all: [APP_ID],
},
},
{
id: 'cases_read',
includeIn: 'read',
name: 'Read',
api: [],
savedObject: {
all: [],
read: [],
},
// using variables with underscores here otherwise when we retrieve them from the kibana
// capabilities in a hook I get type errors regarding boolean | ReadOnly<{[x: string]: boolean}>
ui: ['read_cases'], // uiCapabilities.siem.read_cases
ui: ['crud_cases', 'read_cases'], // uiCapabilities[CASES_FEATURE_ID].crud_cases or read_cases
},
read: {
app: [CASES_FEATURE_ID, 'kibana'],
catalogue: [APP_ID],
cases: {
read: [APP_ID],
},
api: [],
savedObject: {
all: [],
read: [],
},
],
ui: ['read_cases'], // uiCapabilities[CASES_FEATURE_ID].read_cases
},
],
};
},
});
export const getAlertsSubFeature = (ruleTypes: string[]): SubFeatureConfig => ({
name: i18n.translate('xpack.securitySolution.featureRegistry.manageAlertsName', {
@ -108,18 +107,17 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
order: 1100,
category: DEFAULT_APP_CATEGORIES.security,
app: [APP_ID, 'kibana'],
catalogue: ['securitySolution'],
catalogue: [APP_ID],
management: {
insightsAndAlerting: ['triggersActions'],
},
alerting: ruleTypes,
cases: [APP_ID],
subFeatures: [{ ...CASES_SUB_FEATURE } /* , { ...getAlertsSubFeature(ruleTypes) } */],
subFeatures: [],
privileges: {
all: {
app: [APP_ID, 'kibana'],
catalogue: ['securitySolution'],
api: ['securitySolution', 'lists-all', 'lists-read', 'rac'],
catalogue: [APP_ID],
api: [APP_ID, 'lists-all', 'lists-read', 'rac'],
savedObject: {
all: ['alert', 'exception-list', 'exception-list-agnostic', ...savedObjectTypes],
read: [],
@ -136,8 +134,8 @@ export const getKibanaPrivilegesFeaturePrivileges = (ruleTypes: string[]): Kiban
},
read: {
app: [APP_ID, 'kibana'],
catalogue: ['securitySolution'],
api: ['securitySolution', 'lists-read', 'rac'],
catalogue: [APP_ID],
api: [APP_ID, 'lists-read', 'rac'],
savedObject: {
all: [],
read: ['exception-list', 'exception-list-agnostic', ...savedObjectTypes],

View file

@ -33,6 +33,7 @@
"feature": {
"ml": ["all"],
"siem": ["all", "read_alerts", "crud_alerts"],
"securitySolutionCases": ["all"],
"actions": ["read"],
"builtInAlerts": ["all"],
"dev_tools": ["all"]

View file

@ -38,6 +38,7 @@
"feature": {
"ml": ["read"],
"siem": ["all", "read_alerts", "crud_alerts"],
"securitySolutionCases": ["all"],
"actions": ["read"],
"builtInAlerts": ["all"]
},

View file

@ -33,6 +33,7 @@
"feature": {
"ml": ["all"],
"siem": ["all", "read_alerts", "crud_alerts"],
"securitySolutionCases": ["all"],
"actions": ["all"],
"builtInAlerts": ["all"]
},

View file

@ -26,6 +26,7 @@
"feature": {
"ml": ["read"],
"siem": ["read", "read_alerts"],
"securitySolutionCases": ["read"],
"actions": ["read"],
"builtInAlerts": ["read"]
},

View file

@ -36,6 +36,7 @@
"feature": {
"ml": ["read"],
"siem": ["all", "read_alerts", "crud_alerts"],
"securitySolutionCases": ["all"],
"actions": ["read"],
"builtInAlerts": ["all"]
},

View file

@ -36,6 +36,7 @@
"feature": {
"ml": ["read"],
"siem": ["all", "read_alerts", "crud_alerts"],
"securitySolutionCases": ["all"],
"actions": ["all"],
"builtInAlerts": ["all"]
},

View file

@ -26,6 +26,7 @@
"feature": {
"ml": ["read"],
"siem": ["read", "read_alerts"],
"securitySolutionCases": ["read"],
"actions": ["read"],
"builtInAlerts": ["read"]
},

View file

@ -28,6 +28,7 @@
"feature": {
"ml": ["read"],
"siem": ["read", "read_alerts"],
"securitySolutionCases": ["read"],
"actions": ["read"],
"builtInAlerts": ["read"]
},

View file

@ -100,7 +100,7 @@ import aadFieldConversion from './lib/detection_engine/routes/index/signal_aad_m
import { alertsFieldMap } from './lib/detection_engine/rule_types/field_maps/alerts';
import { rulesFieldMap } from './lib/detection_engine/rule_types/field_maps/rules';
import { RuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/rule_execution_log_client';
import { getKibanaPrivilegesFeaturePrivileges } from './features';
import { getKibanaPrivilegesFeaturePrivileges, getCasesKibanaFeature } from './features';
import { EndpointMetadataService } from './endpoint/services/metadata';
import { CreateRuleOptions } from './lib/detection_engine/rule_types/types';
import { ctiFieldMap } from './lib/detection_engine/rule_types/field_maps/cti';
@ -305,6 +305,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
];
plugins.features.registerKibanaFeature(getKibanaPrivilegesFeaturePrivileges(ruleTypes));
plugins.features.registerKibanaFeature(getCasesKibanaFeature());
// Continue to register legacy rules against alerting client exposed through rule-registry
if (this.setupPlugins.alerting != null) {

View file

@ -116,6 +116,7 @@ export default function ({ getService }: FtrProviderContext) {
'osquery',
'uptime',
'siem',
'securitySolutionCases',
'fleet',
].sort()
);

View file

@ -32,8 +32,9 @@ export default function ({ getService }: FtrProviderContext) {
actions: ['all', 'read'],
stackAlerts: ['all', 'read'],
ml: ['all', 'read'],
siem: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_all', 'cases_read'],
siem: ['all', 'read'],
uptime: ['all', 'read'],
securitySolutionCases: ['all', 'read'],
infrastructure: ['all', 'read'],
logs: ['all', 'read'],
apm: ['all', 'read', 'minimal_all', 'minimal_read', 'alerts_all', 'alerts_read'],

View file

@ -38,6 +38,7 @@ export default function ({ getService }: FtrProviderContext) {
osquery: ['all', 'read'],
ml: ['all', 'read'],
siem: ['all', 'read'],
securitySolutionCases: ['all', 'read'],
fleet: ['all', 'read'],
stackAlerts: ['all', 'read'],
actions: ['all', 'read'],

View file

@ -37,6 +37,7 @@ const secAll: Role = {
{
feature: {
siem: ['all'],
securitySolutionCases: ['all'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -66,7 +67,8 @@ const secAllCasesRead: Role = {
kibana: [
{
feature: {
siem: ['minimal_all', 'cases_read'],
siem: ['all'],
securitySolutionCases: ['read'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -96,7 +98,7 @@ const secAllCasesNone: Role = {
kibana: [
{
feature: {
siem: ['minimal_all'],
siem: ['all'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -126,7 +128,8 @@ const secReadCasesAll: Role = {
kibana: [
{
feature: {
siem: ['minimal_read', 'cases_all'],
siem: ['read'],
securitySolutionCases: ['all'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -156,7 +159,8 @@ const secReadCasesRead: Role = {
kibana: [
{
feature: {
siem: ['minimal_read', 'cases_read'],
siem: ['read'],
securitySolutionCases: ['read'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -187,6 +191,7 @@ const secRead: Role = {
{
feature: {
siem: ['read'],
securitySolutionCases: ['read'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -216,7 +221,7 @@ const secReadCasesNone: Role = {
kibana: [
{
feature: {
siem: ['minimal_read'],
siem: ['read'],
actions: ['all'],
actionsSimulators: ['all'],
},

View file

@ -37,6 +37,7 @@ const secAll: Role = {
{
feature: {
siem: ['all'],
securitySolutionCases: ['all'],
actions: ['all'],
actionsSimulators: ['all'],
},
@ -67,6 +68,7 @@ const secRead: Role = {
{
feature: {
siem: ['read'],
securitySolutionCases: ['read'],
actions: ['all'],
actionsSimulators: ['all'],
},