From cf6f0fde9fa6bbdc81b6e78aeb5cb2e43d3ca185 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 27 Sep 2021 15:27:17 -0400 Subject: [PATCH 01/16] cases sub feature deprecations --- .../security_solution/common/constants.ts | 1 + .../deprecation_privileges/index.test.ts | 297 ++++++++++++++++++ .../server/deprecation_privileges/index.ts | 145 +++++++++ .../security_solution/server/plugin.ts | 7 + 4 files changed, 450 insertions(+) create mode 100644 x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts create mode 100644 x-pack/plugins/security_solution/server/deprecation_privileges/index.ts diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 2e2dffa05c9fb..83e8e649e6c8d 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -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'; diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts new file mode 100644 index 0000000000000..8f8e6f9838764 --- /dev/null +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -0,0 +1,297 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { updateSecuritySolutionPrivileges } from '.'; + +describe('deprecations', () => { + describe('create cases privileges from siem privileges without cases sub-feature', () => { + test('should be empty if siem privileges is an empty array', () => { + expect(updateSecuritySolutionPrivileges([])).toMatchInlineSnapshot(`Object {}`); + }); + + test('creates cases privilege ["all"] when siem privilege is ["all"]', () => { + expect(updateSecuritySolutionPrivileges(['all'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["all", "read"]', () => { + expect(updateSecuritySolutionPrivileges(['all', 'read'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["read", "all"]', () => { + expect(updateSecuritySolutionPrivileges(['read', 'all'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["read"] when siem privilege is ["read"]', () => { + expect(updateSecuritySolutionPrivileges(['read'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "read", + ], + } + `); + }); + }); + + describe('create cases privileges from siem privileges with cases sub-feature', () => { + test('No cases privilege when siem privilege is ["minimal_all"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all'])).toMatchInlineSnapshot(` + Object { + "siem": Array [ + "all", + ], + } + `); + }); + + test('No cases privilege when siem privilege is ["minimal_read"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_read'])).toMatchInlineSnapshot(` + Object { + "siem": Array [ + "read", + ], + } + `); + }); + + test('No cases privilege when siem privilege is ["minimal_read", "minimal_all"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_read', 'minimal_all'])) + .toMatchInlineSnapshot(` + Object { + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["minimal_all", "all"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'all'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["all", "minimal_read"]', () => { + expect(updateSecuritySolutionPrivileges(['all', 'minimal_read'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "read", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["minimal_all", "cases_all"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'cases_all'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is [minimal_all, cases_read, cases_all]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'cases_read', 'cases_all'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is [minimal_all, cases_all, cases_read]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'cases_all', 'cases_read'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["all", "cases_read"]', () => { + expect(updateSecuritySolutionPrivileges(['all', 'cases_read'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["read"] when siem privilege is ["minimal_all", "read"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'read'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["read"] when siem privilege is ["read", "minimal_read"]', () => { + expect(updateSecuritySolutionPrivileges(['read', 'minimal_read'])).toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "read", + ], + } + `); + }); + + test('creates cases privilege ["read"] when siem privilege is ["minimal_all", "cases_read"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'cases_read'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["read"] when siem privilege is ["minimal_all", "read", "cases_read"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_all', 'read', 'cases_read'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "all", + ], + } + `); + }); + + test('creates cases privilege ["read"] when siem privilege is ["minimal_read", "cases_read"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_read', 'cases_read'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "read", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is ["minimal_read", "cases_all"]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_read', 'cases_all'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "read", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is [minimal_read, cases_read, cases_all]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_read', 'cases_read', 'cases_all'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "read", + ], + } + `); + }); + + test('creates cases privilege ["all"] when siem privilege is [minimal_read, cases_all, cases_read]', () => { + expect(updateSecuritySolutionPrivileges(['minimal_read', 'cases_all', 'cases_read'])) + .toMatchInlineSnapshot(` + Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "read", + ], + } + `); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts new file mode 100644 index 0000000000000..097259fe4cb5b --- /dev/null +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { DeprecationsDetails, DeprecationsServiceSetup } from '../../../../../src/core/server'; +import type { PrivilegeDeprecationsServices } from '../../../security/common/model'; +import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../common/constants'; + +interface Deps { + deprecationsService: DeprecationsServiceSetup; + getKibanaRolesByFeatureId?: PrivilegeDeprecationsServices['getKibanaRolesByFeatureId']; +} + +export const updateSecuritySolutionPrivileges = ( + siemPrivileges: string[] +): Partial> => { + const newSiemPrivileges = + siemPrivileges.includes('minimal_read') || siemPrivileges.includes('minimal_all') + ? siemPrivileges.reduce((acc, priv) => { + if (!acc.includes('all') && priv === 'minimal_all') { + return ['all']; + } else if (!acc.includes('read') && !acc.includes('all') && priv === 'minimal_read') { + return ['read']; + } + return acc; + }, []) + : siemPrivileges.reduce((acc, priv) => { + if (!acc.includes('all') && priv === 'all') { + return ['all']; + } else if (!acc.includes('read') && !acc.includes('all') && priv === 'read') { + return ['read']; + } + return acc; + }, []); + + const casePrivileges = + siemPrivileges.includes('minimal_read') || siemPrivileges.includes('minimal_all') + ? siemPrivileges.reduce((acc, priv) => { + if (priv === 'cases_all' || priv === 'all') { + return ['all']; + } else if ((priv === 'cases_read' || priv === 'read') && !acc.includes('all')) { + return ['read']; + } + return acc; + }, []) + : newSiemPrivileges; + + return { + ...(newSiemPrivileges.length > 0 + ? { + [SERVER_APP_ID]: newSiemPrivileges, + } + : {}), + ...(casePrivileges.length > 0 + ? { + [CASES_FEATURE_ID]: casePrivileges, + } + : {}), + }; +}; + +export const registerPrivilegeDeprecations = ({ + deprecationsService, + getKibanaRolesByFeatureId, +}: Deps) => { + deprecationsService.registerDeprecations({ + getDeprecations: async (context) => { + if (!getKibanaRolesByFeatureId) { + return []; + } + const responseRoles = await getKibanaRolesByFeatureId({ + context, + featureId: 'siem', + }); + + if (responseRoles.errors && responseRoles.errors.length > 0) { + return responseRoles.errors; + } + + const roles = responseRoles.roles ?? []; + return roles.map((role) => { + const { metadata, elasticsearch, kibana } = role; + + const updatedKibana = kibana.map((privilege) => { + const { siem, ...otherFeatures } = privilege.feature; + const privilegeContainsSiem = Array.isArray(siem) && siem.length > 0; + + if (privilegeContainsSiem) { + return { + ...privilege, + feature: { + ...otherFeatures, + ...updateSecuritySolutionPrivileges(siem), + }, + }; + } + return privilege; + }); + + const updatedRole = { + metadata, + elasticsearch, + kibana: updatedKibana, + }; + + return { + title: i18n.translate( + 'xpack.securitySolution.deprecation.casesSubfeaturePrivileges.title', + { + defaultMessage: 'Deprecate cases sub-feature privileges in Security', + } + ), + message: i18n.translate( + 'xpack.securitySolution.deprecation.ccasesSubfeaturePrivileges.message', + { + defaultMessage: + 'The "securitySolutions" feature privilege has been populated with siem feature or cases sub feature if existing.', + } + ), + level: 'warning', + correctiveActions: { + api: { + method: 'PUT', + path: `/api/security/role/${encodeURIComponent(role.name)}`, + body: updatedRole, + }, + manualSteps: [], + }, + }; + }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 14da8ca650960..ebd8316dd7ffd 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -104,6 +104,7 @@ import { getKibanaPrivilegesFeaturePrivileges } 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'; +import { registerPrivilegeDeprecations } from './deprecation_privileges'; export interface SetupPlugins { alerting: AlertingSetup; @@ -351,6 +352,12 @@ export class Plugin implements IPlugin Date: Tue, 28 Sep 2021 14:49:02 -0400 Subject: [PATCH 02/16] review and testing -> all is always winning --- .../server/deprecation_privileges/index.test.ts | 2 +- .../server/deprecation_privileges/index.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index 8f8e6f9838764..f68690a7f203c 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -124,7 +124,7 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "read", + "all", ], } `); diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index 097259fe4cb5b..967335a976ac0 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -29,9 +29,13 @@ export const updateSecuritySolutionPrivileges = ( const newSiemPrivileges = siemPrivileges.includes('minimal_read') || siemPrivileges.includes('minimal_all') ? siemPrivileges.reduce((acc, priv) => { - if (!acc.includes('all') && priv === 'minimal_all') { + if (!acc.includes('all') && (priv === 'minimal_all' || priv === 'all')) { return ['all']; - } else if (!acc.includes('read') && !acc.includes('all') && priv === 'minimal_read') { + } else if ( + !acc.includes('read') && + !acc.includes('all') && + (priv === 'minimal_read' || priv === 'read') + ) { return ['read']; } return acc; From 6a9be17f5808259c8c6e6fd2261b137a8d170fdc Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 28 Sep 2021 18:52:09 -0400 Subject: [PATCH 03/16] make it more readable from Jonathan Buttner s suggestion --- .../server/deprecation_privileges/index.ts | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index 967335a976ac0..977027452980d 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -26,40 +26,45 @@ interface Deps { export const updateSecuritySolutionPrivileges = ( siemPrivileges: string[] ): Partial> => { - const newSiemPrivileges = - siemPrivileges.includes('minimal_read') || siemPrivileges.includes('minimal_all') - ? siemPrivileges.reduce((acc, priv) => { - if (!acc.includes('all') && (priv === 'minimal_all' || priv === 'all')) { - return ['all']; - } else if ( - !acc.includes('read') && - !acc.includes('all') && - (priv === 'minimal_read' || priv === 'read') - ) { - return ['read']; - } - return acc; - }, []) - : siemPrivileges.reduce((acc, priv) => { - if (!acc.includes('all') && priv === 'all') { - return ['all']; - } else if (!acc.includes('read') && !acc.includes('all') && priv === 'read') { - return ['read']; - } - return acc; - }, []); - - const casePrivileges = - siemPrivileges.includes('minimal_read') || siemPrivileges.includes('minimal_all') - ? siemPrivileges.reduce((acc, priv) => { - if (priv === 'cases_all' || priv === 'all') { - return ['all']; - } else if ((priv === 'cases_read' || priv === 'read') && !acc.includes('all')) { - return ['read']; - } - return acc; - }, []) - : newSiemPrivileges; + const siemPrivs = new Set(); + const casesPrivs = new Set(); + + for (const priv of siemPrivileges) { + switch (priv) { + case 'all': + siemPrivs.add('all'); + casesPrivs.add('all'); + break; + case 'read': + siemPrivs.add('read'); + casesPrivs.add('read'); + break; + case 'minimal_all': + siemPrivs.add('all'); + break; + case 'minimal_read': + siemPrivs.add('read'); + break; + case 'cases_all': + casesPrivs.add('all'); + break; + case 'cases_read': + casesPrivs.add('read'); + break; + } + } + + const newSiemPrivileges: string[] = siemPrivs.has('all') + ? ['all'] + : siemPrivs.has('read') + ? ['read'] + : []; + + const casePrivileges: string[] = casesPrivs.has('all') + ? ['all'] + : casesPrivs.has('read') + ? ['read'] + : []; return { ...(newSiemPrivileges.length > 0 From d45fc753186471917b98a1b25cf6ed00f8e955b8 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 28 Sep 2021 18:53:40 -0400 Subject: [PATCH 04/16] too much header --- .../server/deprecation_privileges/index.test.ts | 6 ------ .../server/deprecation_privileges/index.ts | 7 ------- 2 files changed, 13 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index f68690a7f203c..e517dec1b72e8 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -4,12 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ import { updateSecuritySolutionPrivileges } from '.'; diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index 977027452980d..5078f7e257b46 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -5,13 +5,6 @@ * 2.0. */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - import { i18n } from '@kbn/i18n'; import { DeprecationsDetails, DeprecationsServiceSetup } from '../../../../../src/core/server'; From e396221f62737cccae1963cad3bfa9cad4878017 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:32:39 -0400 Subject: [PATCH 05/16] add better logs --- .../server/deprecation_privileges/index.ts | 133 ++++++++++++------ .../security_solution/server/plugin.ts | 1 + 2 files changed, 88 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index c71134dece40d..eb103b4d3f370 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { Logger } from 'src/core/server'; import { DeprecationsDetails, DeprecationsServiceSetup } from '../../../../../src/core/server'; import type { PrivilegeDeprecationsService } from '../../../security/common/model'; @@ -14,6 +15,7 @@ import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../common/constants'; interface Deps { deprecationsService: DeprecationsServiceSetup; getKibanaRolesByFeatureId?: PrivilegeDeprecationsService['getKibanaRolesByFeatureId']; + logger: Logger; } export const updateSecuritySolutionPrivileges = ( @@ -76,11 +78,13 @@ export const updateSecuritySolutionPrivileges = ( export const registerPrivilegeDeprecations = ({ deprecationsService, getKibanaRolesByFeatureId, + logger, }: Deps) => { deprecationsService.registerDeprecations({ getDeprecations: async (context) => { + let deprecatedRoles: DeprecationsDetails[] = []; if (!getKibanaRolesByFeatureId) { - return []; + return deprecatedRoles; } const responseRoles = await getKibanaRolesByFeatureId({ context, @@ -91,57 +95,94 @@ export const registerPrivilegeDeprecations = ({ return responseRoles.errors; } - const roles = responseRoles.roles ?? []; - return roles.map((role) => { - const { metadata, elasticsearch, kibana } = role; + try { + const roles = responseRoles.roles ?? []; + deprecatedRoles = roles.map((role) => { + const { metadata, elasticsearch, kibana } = role; - const updatedKibana = kibana.map((privilege) => { - const { siem, ...otherFeatures } = privilege.feature; - const privilegeContainsSiem = Array.isArray(siem) && siem.length > 0; + const updatedKibana = kibana.map((privilege) => { + const { siem, ...otherFeatures } = privilege.feature; + const privilegeContainsSiem = Array.isArray(siem) && siem.length > 0; - if (privilegeContainsSiem) { - return { - ...privilege, - feature: { - ...otherFeatures, - ...updateSecuritySolutionPrivileges(siem), - }, - }; - } - return privilege; - }); + if (privilegeContainsSiem) { + return { + ...privilege, + feature: { + ...otherFeatures, + ...updateSecuritySolutionPrivileges(siem), + }, + }; + } + return privilege; + }); - const updatedRole = { - metadata, - elasticsearch, - kibana: updatedKibana, - }; + const updatedRole = { + metadata, + elasticsearch, + kibana: updatedKibana, + }; - return { - title: i18n.translate( - 'xpack.securitySolution.deprecation.casesSubfeaturePrivileges.title', - { - defaultMessage: 'Deprecate cases sub-feature privileges in Security', - } - ), - message: i18n.translate( - 'xpack.securitySolution.deprecation.ccasesSubfeaturePrivileges.message', - { - defaultMessage: - 'The "securitySolutions" feature privilege has been populated with siem feature or cases sub feature if existing.', - } - ), - level: 'warning', - correctiveActions: { - api: { - method: 'PUT', - path: `/api/security/role/${encodeURIComponent(role.name)}`, - body: updatedRole, + return { + title: i18n.translate( + 'xpack.securitySolution.deprecation.casesSubfeaturePrivileges.title', + { + defaultMessage: 'Deprecate cases sub-feature privileges in Security', + } + ), + message: i18n.translate( + 'xpack.securitySolution.deprecation.ccasesSubfeaturePrivileges.message', + { + defaultMessage: + 'The "securitySolutions" feature privilege has been populated with siem feature or cases sub feature if existing.', + } + ), + level: 'warning', + correctiveActions: { + api: { + method: 'PUT', + path: `/api/security/role/${encodeURIComponent(role.name)}`, + body: updatedRole, + }, + manualSteps: [], + }, + }; + }); + } catch (err) { + const errMsg = err instanceof Error ? err.message : 'n/a'; + const message = i18n.translate( + 'xpack.securitySolutions.privilegeDeprecations.error.convertingRoles.message', + { + defaultMessage: `Failed to create cases roles from siem roles, unexpected error: {message}`, + values: { + message: errMsg, + }, + } + ); + logger.error( + `Failed to create cases roles from siem roles, unexpected error: ${errMsg ?? ''}` + ); + return [ + { + title: i18n.translate('xpack.securitySolutions.privilegeDeprecations.error.title', { + defaultMessage: `Error in security solution to deprecate cases sub feature`, + }), + level: 'fetch_error', + message, + correctiveActions: { + manualSteps: [ + i18n.translate( + 'xpack.securitySolutions.privilegeDeprecations.manualSteps.message', + { + defaultMessage: + 'A user will have to set cases privileges manually in your associated role', + } + ), + ], }, - manualSteps: [], }, - }; - }); + ]; + } + return deprecatedRoles; }, }); }; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 62ba149ccb809..a6a8e0824455b 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -375,6 +375,7 @@ export class Plugin implements IPlugin Date: Wed, 6 Oct 2021 10:32:24 -0400 Subject: [PATCH 06/16] fix i18n --- .../server/deprecation_privileges/index.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index eb103b4d3f370..d1fc3f9089251 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -124,13 +124,13 @@ export const registerPrivilegeDeprecations = ({ return { title: i18n.translate( - 'xpack.securitySolution.deprecation.casesSubfeaturePrivileges.title', + 'xpack.securitySolution.privilegeDeprecations.casesSubFeaturePrivileges.title', { - defaultMessage: 'Deprecate cases sub-feature privileges in Security', + defaultMessage: 'Deprecate cases sub-feature privileges in security solution', } ), message: i18n.translate( - 'xpack.securitySolution.deprecation.ccasesSubfeaturePrivileges.message', + 'xpack.securitySolution.privilegeDeprecations.casesSubFeaturePrivileges.message', { defaultMessage: 'The "securitySolutions" feature privilege has been populated with siem feature or cases sub feature if existing.', @@ -150,7 +150,7 @@ export const registerPrivilegeDeprecations = ({ } catch (err) { const errMsg = err instanceof Error ? err.message : 'n/a'; const message = i18n.translate( - 'xpack.securitySolutions.privilegeDeprecations.error.convertingRoles.message', + 'xpack.securitySolution.privilegeDeprecations.error.casesSubFeaturePrivileges.message', { defaultMessage: `Failed to create cases roles from siem roles, unexpected error: {message}`, values: { @@ -163,15 +163,18 @@ export const registerPrivilegeDeprecations = ({ ); return [ { - title: i18n.translate('xpack.securitySolutions.privilegeDeprecations.error.title', { - defaultMessage: `Error in security solution to deprecate cases sub feature`, - }), + title: i18n.translate( + 'xpack.securitySolution.privilegeDeprecations.error.casesSubFeaturePrivileges.title', + { + defaultMessage: `Error in security solution to deprecate cases sub feature`, + } + ), level: 'fetch_error', message, correctiveActions: { manualSteps: [ i18n.translate( - 'xpack.securitySolutions.privilegeDeprecations.manualSteps.message', + 'xpack.securitySolution.privilegeDeprecations.manualSteps.casesSubFeaturePrivileges.message', { defaultMessage: 'A user will have to set cases privileges manually in your associated role', From 6420a359ffe7aade65146f4e2638bb193140ad57 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 8 Oct 2021 16:38:11 -0400 Subject: [PATCH 07/16] Update x-pack/plugins/security_solution/server/deprecation_privileges/index.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../security_solution/server/deprecation_privileges/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index d1fc3f9089251..142a381fb1f3f 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -133,7 +133,8 @@ export const registerPrivilegeDeprecations = ({ 'xpack.securitySolution.privilegeDeprecations.casesSubFeaturePrivileges.message', { defaultMessage: - 'The "securitySolutions" feature privilege has been populated with siem feature or cases sub feature if existing.', + 'The "Security" feature will be split into two separate features in 8.0. The "{roleName}" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.', + values: { roleName }, } ), level: 'warning', From 36cd20591367de628dfeb2de4c1cf8e3d0e3e26e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 8 Oct 2021 16:38:19 -0400 Subject: [PATCH 08/16] Update x-pack/plugins/security_solution/server/deprecation_privileges/index.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../security_solution/server/deprecation_privileges/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index 142a381fb1f3f..ba3a5ca7bd735 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -126,7 +126,8 @@ export const registerPrivilegeDeprecations = ({ title: i18n.translate( 'xpack.securitySolution.privilegeDeprecations.casesSubFeaturePrivileges.title', { - defaultMessage: 'Deprecate cases sub-feature privileges in security solution', + defaultMessage: 'The "{roleName}" role needs to be updated', + values: { roleName }, } ), message: i18n.translate( From 0096ba46cb1b5b1d82d69c9ba11a36f45b1478e7 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 8 Oct 2021 16:38:39 -0400 Subject: [PATCH 09/16] Update x-pack/plugins/security_solution/server/deprecation_privileges/index.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../security_solution/server/deprecation_privileges/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index ba3a5ca7bd735..e45c76e1f6f79 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -98,7 +98,7 @@ export const registerPrivilegeDeprecations = ({ try { const roles = responseRoles.roles ?? []; deprecatedRoles = roles.map((role) => { - const { metadata, elasticsearch, kibana } = role; + const { metadata, elasticsearch, kibana, name: roleName } = role; const updatedKibana = kibana.map((privilege) => { const { siem, ...otherFeatures } = privilege.feature; From c9d8e95a13721b51b580fca790b7b7b298e88b22 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 8 Oct 2021 16:38:53 -0400 Subject: [PATCH 10/16] Update x-pack/plugins/security_solution/server/deprecation_privileges/index.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../security_solution/server/deprecation_privileges/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index e45c76e1f6f79..c55a3e70b7923 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -139,6 +139,7 @@ export const registerPrivilegeDeprecations = ({ } ), level: 'warning', + deprecationType: 'feature', correctiveActions: { api: { method: 'PUT', From 0b9783d745122e76f212fe07b1cb84fb56e6b696 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 11 Oct 2021 15:25:10 -0400 Subject: [PATCH 11/16] it should work now --- .../deprecation_privileges/index.test.ts | 220 ++++++++++++++++-- .../server/deprecation_privileges/index.ts | 28 +-- 2 files changed, 216 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index e517dec1b72e8..4785e47d6e729 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -5,7 +5,14 @@ * 2.0. */ -import { updateSecuritySolutionPrivileges } from '.'; +import { + DeprecationsServiceSetup, + GetDeprecationsContext, + RegisterDeprecationsConfig, +} from 'src/core/server'; +import { loggingSystemMock } from 'src/core/server/mocks'; + +import { registerPrivilegeDeprecations, updateSecuritySolutionPrivileges } from '.'; describe('deprecations', () => { describe('create cases privileges from siem privileges without cases sub-feature', () => { @@ -34,6 +41,7 @@ describe('deprecations', () => { ], "siem": Array [ "all", + "read", ], } `); @@ -46,6 +54,7 @@ describe('deprecations', () => { "all", ], "siem": Array [ + "read", "all", ], } @@ -71,7 +80,7 @@ describe('deprecations', () => { expect(updateSecuritySolutionPrivileges(['minimal_all'])).toMatchInlineSnapshot(` Object { "siem": Array [ - "all", + "minimal_all", ], } `); @@ -81,7 +90,7 @@ describe('deprecations', () => { expect(updateSecuritySolutionPrivileges(['minimal_read'])).toMatchInlineSnapshot(` Object { "siem": Array [ - "read", + "minimal_read", ], } `); @@ -92,7 +101,8 @@ describe('deprecations', () => { .toMatchInlineSnapshot(` Object { "siem": Array [ - "all", + "minimal_read", + "minimal_all", ], } `); @@ -105,6 +115,7 @@ describe('deprecations', () => { "all", ], "siem": Array [ + "minimal_all", "all", ], } @@ -119,6 +130,7 @@ describe('deprecations', () => { ], "siem": Array [ "all", + "minimal_read", ], } `); @@ -131,7 +143,8 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "all", + "minimal_all", + "cases_all", ], } `); @@ -145,7 +158,9 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "all", + "minimal_all", + "cases_read", + "cases_all", ], } `); @@ -159,7 +174,9 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "all", + "minimal_all", + "cases_all", + "cases_read", ], } `); @@ -173,6 +190,7 @@ describe('deprecations', () => { ], "siem": Array [ "all", + "cases_read", ], } `); @@ -185,7 +203,8 @@ describe('deprecations', () => { "read", ], "siem": Array [ - "all", + "minimal_all", + "read", ], } `); @@ -199,6 +218,7 @@ describe('deprecations', () => { ], "siem": Array [ "read", + "minimal_read", ], } `); @@ -212,7 +232,8 @@ describe('deprecations', () => { "read", ], "siem": Array [ - "all", + "minimal_all", + "cases_read", ], } `); @@ -226,7 +247,9 @@ describe('deprecations', () => { "read", ], "siem": Array [ - "all", + "minimal_all", + "read", + "cases_read", ], } `); @@ -240,7 +263,8 @@ describe('deprecations', () => { "read", ], "siem": Array [ - "read", + "minimal_read", + "cases_read", ], } `); @@ -254,7 +278,8 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "read", + "minimal_read", + "cases_all", ], } `); @@ -268,7 +293,9 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "read", + "minimal_read", + "cases_read", + "cases_all", ], } `); @@ -282,10 +309,175 @@ describe('deprecations', () => { "all", ], "siem": Array [ - "read", + "minimal_read", + "cases_all", + "cases_read", ], } `); }); }); + + describe('registerPrivilegeDeprecations', () => { + const mockContext = { + esClient: jest.fn(), + savedObjectsClient: jest.fn(), + } as unknown as GetDeprecationsContext; + const getDeprecations = jest.fn(); + const getKibanaRolesByFeatureId = jest.fn(); + const mockDeprecationsService: DeprecationsServiceSetup = { + registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => { + getDeprecations.mockImplementation(deprecationContext.getDeprecations); + }, + }; + + beforeAll(() => { + registerPrivilegeDeprecations({ + deprecationsService: mockDeprecationsService, + getKibanaRolesByFeatureId, + logger: loggingSystemMock.createLogger(), + }); + }); + beforeEach(() => { + getKibanaRolesByFeatureId.mockReset(); + }); + + test('getDeprecations return the errors from getKibanaRolesByFeatureId', async () => { + const errorResponse = { + errors: [ + { + correctiveActions: { + manualSteps: [ + "A user with the 'manage_security' cluster privilege is required to perform this check.", + ], + }, + level: 'fetch_error', + message: 'Error retrieving roles for privilege deprecations: Test error', + title: 'Error in privilege deprecations services', + }, + ], + }; + getKibanaRolesByFeatureId.mockResolvedValue(errorResponse); + const response = await getDeprecations(mockContext); + expect(response).toEqual(errorResponse.errors); + }); + + test('getDeprecations return empty array when securitySolutionCases privileges are already set up', async () => { + getKibanaRolesByFeatureId.mockResolvedValue({ + roles: [ + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + bar: ['bar-privilege-1'], + securitySolutionCases: ['read'], + siem: ['minimal_read', 'cases_read'], + }, + spaces: ['securitySolutions'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'first_role', + transient_metadata: { + enabled: true, + }, + }, + ], + }); + const response = await getDeprecations(mockContext); + expect(response).toMatchInlineSnapshot(`Array []`); + }); + + test('happy path build securitySolutionCases privileges from siem privileges', async () => { + getKibanaRolesByFeatureId.mockResolvedValue({ + roles: [ + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + bar: ['bar-privilege-1'], + siem: ['minimal_all', 'cases_read'], + }, + spaces: ['securitySolutions'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'first_role', + transient_metadata: { + enabled: true, + }, + }, + ], + }); + const response = await getDeprecations(mockContext); + expect(response).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "api": Object { + "body": Object { + "elasticsearch": Object { + "cluster": Array [], + "indices": Array [], + "run_as": Array [], + }, + "kibana": Array [ + Object { + "base": Array [], + "feature": Object { + "bar": Array [ + "bar-privilege-1", + ], + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "minimal_all", + "cases_read", + ], + }, + "spaces": Array [ + "securitySolutions", + ], + }, + ], + "metadata": Object { + "_reserved": true, + }, + }, + "method": "PUT", + "omitContextFromBody": true, + "path": "/api/security/role/first_role", + }, + "manualSteps": Array [], + }, + "deprecationType": "feature", + "level": "warning", + "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"first_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", + "title": "The \\"first_role\\" role needs to be updated", + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index c55a3e70b7923..13dc7cf5f489b 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -21,25 +21,16 @@ interface Deps { export const updateSecuritySolutionPrivileges = ( siemPrivileges: string[] ): Partial> => { - const siemPrivs = new Set(); const casesPrivs = new Set(); for (const priv of siemPrivileges) { switch (priv) { case 'all': - siemPrivs.add('all'); casesPrivs.add('all'); break; case 'read': - siemPrivs.add('read'); casesPrivs.add('read'); break; - case 'minimal_all': - siemPrivs.add('all'); - break; - case 'minimal_read': - siemPrivs.add('read'); - break; case 'cases_all': casesPrivs.add('all'); break; @@ -49,12 +40,6 @@ export const updateSecuritySolutionPrivileges = ( } } - const newSiemPrivileges: string[] = siemPrivs.has('all') - ? ['all'] - : siemPrivs.has('read') - ? ['read'] - : []; - const casePrivileges: string[] = casesPrivs.has('all') ? ['all'] : casesPrivs.has('read') @@ -62,9 +47,9 @@ export const updateSecuritySolutionPrivileges = ( : []; return { - ...(newSiemPrivileges.length > 0 + ...(siemPrivileges.length > 0 ? { - [SERVER_APP_ID]: newSiemPrivileges, + [SERVER_APP_ID]: siemPrivileges, } : {}), ...(casePrivileges.length > 0 @@ -94,7 +79,13 @@ export const registerPrivilegeDeprecations = ({ if (responseRoles.errors && responseRoles.errors.length > 0) { return responseRoles.errors; } - + if ( + responseRoles.roles?.some((role) => + role.kibana.some((privilege) => privilege.feature[CASES_FEATURE_ID] != null) + ) + ) { + return []; + } try { const roles = responseRoles.roles ?? []; deprecatedRoles = roles.map((role) => { @@ -145,6 +136,7 @@ export const registerPrivilegeDeprecations = ({ method: 'PUT', path: `/api/security/role/${encodeURIComponent(role.name)}`, body: updatedRole, + omitContextFromBody: true, }, manualSteps: [], }, From 4cd018a4b9ec0305005224110bd6739a805821b8 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 13 Oct 2021 09:59:49 -0400 Subject: [PATCH 12/16] forget to take in consideration multiple roles --- .../deprecation_privileges/index.test.ts | 135 ++++++++++++++++++ .../server/deprecation_privileges/index.ts | 22 +-- 2 files changed, 147 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index 4785e47d6e729..ab5056ab73ca1 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -479,5 +479,140 @@ describe('deprecations', () => { ] `); }); + + test('getDeprecations with multiple roles and get a deprecation on the role "second_role" in the space of "readSecuritySolution_2"', async () => { + getKibanaRolesByFeatureId.mockResolvedValue({ + roles: [ + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + bar: ['bar-privilege-1'], + securitySolutionCases: ['read'], + siem: ['minimal_read', 'cases_read'], + }, + spaces: ['readSecuritySolution_1'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'first_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + bar: ['bar-privilege-1'], + siem: ['minimal_read', 'cases_read'], + }, + spaces: ['readSecuritySolution_2'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'second_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + bar: ['bar-privilege-1'], + siem: ['minimal_all'], + }, + spaces: ['allSecuritySolution'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'third_role', + transient_metadata: { + enabled: true, + }, + }, + ], + }); + const response = await getDeprecations(mockContext); + expect(response).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "api": Object { + "body": Object { + "elasticsearch": Object { + "cluster": Array [], + "indices": Array [], + "run_as": Array [], + }, + "kibana": Array [ + Object { + "base": Array [], + "feature": Object { + "bar": Array [ + "bar-privilege-1", + ], + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "minimal_read", + "cases_read", + ], + }, + "spaces": Array [ + "readSecuritySolution_2", + ], + }, + ], + "metadata": Object { + "_reserved": true, + }, + }, + "method": "PUT", + "omitContextFromBody": true, + "path": "/api/security/role/second_role", + }, + "manualSteps": Array [], + }, + "deprecationType": "feature", + "level": "warning", + "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"second_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", + "title": "The \\"second_role\\" role needs to be updated", + }, + ] + `); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts index 13dc7cf5f489b..803231b236cbd 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { Logger } from 'src/core/server'; import { DeprecationsDetails, DeprecationsServiceSetup } from '../../../../../src/core/server'; -import type { PrivilegeDeprecationsService } from '../../../security/common/model'; +import type { PrivilegeDeprecationsService, Role } from '../../../security/common/model'; import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../common/constants'; interface Deps { @@ -60,6 +60,14 @@ export const updateSecuritySolutionPrivileges = ( }; }; +const SIEM_PRIVILEGES_FOR_CASES = new Set(['all', 'read', 'cases_all', 'cases_read']); +function outdatedSiemRolePredicate(role: Role) { + return role.kibana.some( + ({ feature }) => + !feature[CASES_FEATURE_ID] && feature.siem.some((x) => SIEM_PRIVILEGES_FOR_CASES.has(x)) + ); +} + export const registerPrivilegeDeprecations = ({ deprecationsService, getKibanaRolesByFeatureId, @@ -79,16 +87,10 @@ export const registerPrivilegeDeprecations = ({ if (responseRoles.errors && responseRoles.errors.length > 0) { return responseRoles.errors; } - if ( - responseRoles.roles?.some((role) => - role.kibana.some((privilege) => privilege.feature[CASES_FEATURE_ID] != null) - ) - ) { - return []; - } + try { - const roles = responseRoles.roles ?? []; - deprecatedRoles = roles.map((role) => { + const filteredRoles = (responseRoles.roles ?? []).filter(outdatedSiemRolePredicate); + deprecatedRoles = filteredRoles.map((role) => { const { metadata, elasticsearch, kibana, name: roleName } = role; const updatedKibana = kibana.map((privilege) => { From bfe202ac39442c3b694493a89ecfca745e1d0142 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 13 Oct 2021 10:08:21 -0400 Subject: [PATCH 13/16] add unit test with multiple roles --- .../deprecation_privileges/index.test.ts | 325 ++++++++++++++++++ 1 file changed, 325 insertions(+) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index ab5056ab73ca1..801aeb820e934 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -614,5 +614,330 @@ describe('deprecations', () => { ] `); }); + + test('happy path with multiple siem roles', async () => { + getKibanaRolesByFeatureId.mockResolvedValue({ + roles: [ + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + siem: ['all'], + }, + spaces: ['securitySolution_1'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'first_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + siem: ['read'], + }, + spaces: ['securitySolution_2'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'second_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + siem: ['minimal_all', 'cases_all', 'cases_read'], + }, + spaces: ['securitySolution_3'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'third_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + siem: ['minimal_read', 'cases_read'], + }, + spaces: ['securitySolution_4'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'fourth_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + siem: ['minimal_all'], + }, + spaces: ['securitySolution_5'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'fifth_role', + transient_metadata: { + enabled: true, + }, + }, + { + _transform_error: [], + _unrecognized_applications: [], + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + siem: ['minimal_read'], + }, + spaces: ['securitySolution_6'], + }, + ], + metadata: { + _reserved: true, + }, + name: 'sixth_role', + transient_metadata: { + enabled: true, + }, + }, + ], + }); + const response = await getDeprecations(mockContext); + expect(response).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "api": Object { + "body": Object { + "elasticsearch": Object { + "cluster": Array [], + "indices": Array [], + "run_as": Array [], + }, + "kibana": Array [ + Object { + "base": Array [], + "feature": Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "all", + ], + }, + "spaces": Array [ + "securitySolution_1", + ], + }, + ], + "metadata": Object { + "_reserved": true, + }, + }, + "method": "PUT", + "omitContextFromBody": true, + "path": "/api/security/role/first_role", + }, + "manualSteps": Array [], + }, + "deprecationType": "feature", + "level": "warning", + "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"first_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", + "title": "The \\"first_role\\" role needs to be updated", + }, + Object { + "correctiveActions": Object { + "api": Object { + "body": Object { + "elasticsearch": Object { + "cluster": Array [], + "indices": Array [], + "run_as": Array [], + }, + "kibana": Array [ + Object { + "base": Array [], + "feature": Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "read", + ], + }, + "spaces": Array [ + "securitySolution_2", + ], + }, + ], + "metadata": Object { + "_reserved": true, + }, + }, + "method": "PUT", + "omitContextFromBody": true, + "path": "/api/security/role/second_role", + }, + "manualSteps": Array [], + }, + "deprecationType": "feature", + "level": "warning", + "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"second_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", + "title": "The \\"second_role\\" role needs to be updated", + }, + Object { + "correctiveActions": Object { + "api": Object { + "body": Object { + "elasticsearch": Object { + "cluster": Array [], + "indices": Array [], + "run_as": Array [], + }, + "kibana": Array [ + Object { + "base": Array [], + "feature": Object { + "securitySolutionCases": Array [ + "all", + ], + "siem": Array [ + "minimal_all", + "cases_all", + "cases_read", + ], + }, + "spaces": Array [ + "securitySolution_3", + ], + }, + ], + "metadata": Object { + "_reserved": true, + }, + }, + "method": "PUT", + "omitContextFromBody": true, + "path": "/api/security/role/third_role", + }, + "manualSteps": Array [], + }, + "deprecationType": "feature", + "level": "warning", + "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"third_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", + "title": "The \\"third_role\\" role needs to be updated", + }, + Object { + "correctiveActions": Object { + "api": Object { + "body": Object { + "elasticsearch": Object { + "cluster": Array [], + "indices": Array [], + "run_as": Array [], + }, + "kibana": Array [ + Object { + "base": Array [], + "feature": Object { + "securitySolutionCases": Array [ + "read", + ], + "siem": Array [ + "minimal_read", + "cases_read", + ], + }, + "spaces": Array [ + "securitySolution_4", + ], + }, + ], + "metadata": Object { + "_reserved": true, + }, + }, + "method": "PUT", + "omitContextFromBody": true, + "path": "/api/security/role/fourth_role", + }, + "manualSteps": Array [], + }, + "deprecationType": "feature", + "level": "warning", + "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"fourth_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", + "title": "The \\"fourth_role\\" role needs to be updated", + }, + ] + `); + }); }); }); From 221276b40a49edf289d2236c3cfc0de4a25858de Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:58:45 -0400 Subject: [PATCH 14/16] Update x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../server/deprecation_privileges/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index 801aeb820e934..4959761ba6e97 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -480,7 +480,7 @@ describe('deprecations', () => { `); }); - test('getDeprecations with multiple roles and get a deprecation on the role "second_role" in the space of "readSecuritySolution_2"', async () => { + test('getDeprecations handles multiple roles and filters out any that have already been updated', async () => { getKibanaRolesByFeatureId.mockResolvedValue({ roles: [ { From 61c5cdd4a9351577ab328765dc5bcc69236194f4 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:58:58 -0400 Subject: [PATCH 15/16] Update x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../server/deprecation_privileges/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index 4959761ba6e97..92b55e89d092b 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -615,7 +615,7 @@ describe('deprecations', () => { `); }); - test('happy path with multiple siem roles', async () => { + test('getDeprecations handles multiple roles and filters out any that do not grant access to Cases', async () => { getKibanaRolesByFeatureId.mockResolvedValue({ roles: [ { From a645e1b74b434e71483b04a61a2c36a205654c2e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Wed, 13 Oct 2021 20:20:51 -0400 Subject: [PATCH 16/16] add review about test --- .../deprecation_privileges/index.test.ts | 190 ++---------------- 1 file changed, 17 insertions(+), 173 deletions(-) diff --git a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts index 92b55e89d092b..f61940387c413 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.test.ts @@ -638,7 +638,7 @@ describe('deprecations', () => { metadata: { _reserved: true, }, - name: 'first_role', + name: 'role_siem_all', transient_metadata: { enabled: true, }, @@ -663,7 +663,7 @@ describe('deprecations', () => { metadata: { _reserved: true, }, - name: 'second_role', + name: 'role_siem_read', transient_metadata: { enabled: true, }, @@ -688,7 +688,7 @@ describe('deprecations', () => { metadata: { _reserved: true, }, - name: 'third_role', + name: 'role_siem_minimal_all_cases_all_cases_read', transient_metadata: { enabled: true, }, @@ -713,7 +713,7 @@ describe('deprecations', () => { metadata: { _reserved: true, }, - name: 'fourth_role', + name: 'role_siem_minimal_read_cases_read', transient_metadata: { enabled: true, }, @@ -738,7 +738,7 @@ describe('deprecations', () => { metadata: { _reserved: true, }, - name: 'fifth_role', + name: 'role_siem_minimal_all', transient_metadata: { enabled: true, }, @@ -763,7 +763,7 @@ describe('deprecations', () => { metadata: { _reserved: true, }, - name: 'sixth_role', + name: 'role_siem_minimal_read', transient_metadata: { enabled: true, }, @@ -771,173 +771,17 @@ describe('deprecations', () => { ], }); const response = await getDeprecations(mockContext); - expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveActions": Object { - "api": Object { - "body": Object { - "elasticsearch": Object { - "cluster": Array [], - "indices": Array [], - "run_as": Array [], - }, - "kibana": Array [ - Object { - "base": Array [], - "feature": Object { - "securitySolutionCases": Array [ - "all", - ], - "siem": Array [ - "all", - ], - }, - "spaces": Array [ - "securitySolution_1", - ], - }, - ], - "metadata": Object { - "_reserved": true, - }, - }, - "method": "PUT", - "omitContextFromBody": true, - "path": "/api/security/role/first_role", - }, - "manualSteps": Array [], - }, - "deprecationType": "feature", - "level": "warning", - "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"first_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", - "title": "The \\"first_role\\" role needs to be updated", - }, - Object { - "correctiveActions": Object { - "api": Object { - "body": Object { - "elasticsearch": Object { - "cluster": Array [], - "indices": Array [], - "run_as": Array [], - }, - "kibana": Array [ - Object { - "base": Array [], - "feature": Object { - "securitySolutionCases": Array [ - "read", - ], - "siem": Array [ - "read", - ], - }, - "spaces": Array [ - "securitySolution_2", - ], - }, - ], - "metadata": Object { - "_reserved": true, - }, - }, - "method": "PUT", - "omitContextFromBody": true, - "path": "/api/security/role/second_role", - }, - "manualSteps": Array [], - }, - "deprecationType": "feature", - "level": "warning", - "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"second_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", - "title": "The \\"second_role\\" role needs to be updated", - }, - Object { - "correctiveActions": Object { - "api": Object { - "body": Object { - "elasticsearch": Object { - "cluster": Array [], - "indices": Array [], - "run_as": Array [], - }, - "kibana": Array [ - Object { - "base": Array [], - "feature": Object { - "securitySolutionCases": Array [ - "all", - ], - "siem": Array [ - "minimal_all", - "cases_all", - "cases_read", - ], - }, - "spaces": Array [ - "securitySolution_3", - ], - }, - ], - "metadata": Object { - "_reserved": true, - }, - }, - "method": "PUT", - "omitContextFromBody": true, - "path": "/api/security/role/third_role", - }, - "manualSteps": Array [], - }, - "deprecationType": "feature", - "level": "warning", - "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"third_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", - "title": "The \\"third_role\\" role needs to be updated", - }, - Object { - "correctiveActions": Object { - "api": Object { - "body": Object { - "elasticsearch": Object { - "cluster": Array [], - "indices": Array [], - "run_as": Array [], - }, - "kibana": Array [ - Object { - "base": Array [], - "feature": Object { - "securitySolutionCases": Array [ - "read", - ], - "siem": Array [ - "minimal_read", - "cases_read", - ], - }, - "spaces": Array [ - "securitySolution_4", - ], - }, - ], - "metadata": Object { - "_reserved": true, - }, - }, - "method": "PUT", - "omitContextFromBody": true, - "path": "/api/security/role/fourth_role", - }, - "manualSteps": Array [], - }, - "deprecationType": "feature", - "level": "warning", - "message": "The \\"Security\\" feature will be split into two separate features in 8.0. The \\"fourth_role\\" role grants access to this feature, and it needs to be updated before you upgrade Kibana. This will ensure that users have access to the same features after the upgrade.", - "title": "The \\"fourth_role\\" role needs to be updated", - }, - ] - `); + expect(response).toEqual([ + expect.objectContaining({ title: 'The "role_siem_all" role needs to be updated' }), + expect.objectContaining({ title: 'The "role_siem_read" role needs to be updated' }), + expect.objectContaining({ + title: 'The "role_siem_minimal_all_cases_all_cases_read" role needs to be updated', + }), + expect.objectContaining({ + title: 'The "role_siem_minimal_read_cases_read" role needs to be updated', + }), + // the fifth_role and sixth_role have been filtered out because they do not grant access to Cases + ]); }); }); });