From 33318f215545fcde709c659b739477468860bb8a Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 18 Oct 2021 15:39:07 -0400 Subject: [PATCH 1/8] Deprecate excluding ML privileges --- .../common/licensing/license_features.ts | 5 + .../common/licensing/license_service.test.ts | 17 +- .../common/licensing/license_service.ts | 14 ++ .../nav_control/nav_control_service.test.ts | 2 +- .../security/server/deprecations/index.ts | 1 + .../server/deprecations/ml_privileges.test.ts | 234 ++++++++++++++++++ .../server/deprecations/ml_privileges.ts | 186 ++++++++++++++ x-pack/plugins/security/server/plugin.ts | 8 + 8 files changed, 464 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/security/server/deprecations/ml_privileges.test.ts create mode 100644 x-pack/plugins/security/server/deprecations/ml_privileges.ts diff --git a/x-pack/plugins/security/common/licensing/license_features.ts b/x-pack/plugins/security/common/licensing/license_features.ts index ac80c89ae7be3..edd6a7ea2084b 100644 --- a/x-pack/plugins/security/common/licensing/license_features.ts +++ b/x-pack/plugins/security/common/licensing/license_features.ts @@ -65,6 +65,11 @@ export interface SecurityLicenseFeatures { */ readonly allowRbac: boolean; + /** + * Indicates if Machine Learning features are available. + */ + readonly allowML: boolean; + /** * Indicates whether we allow sub-feature privileges. */ diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index cdc80c1a038f1..5d7ce321f9ef6 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -28,6 +28,7 @@ describe('license features', function () { allowRbac: false, allowSubFeaturePrivileges: false, allowAuditLogging: false, + allowML: false, allowLegacyAuditLogging: false, }); }); @@ -51,6 +52,7 @@ describe('license features', function () { allowRbac: false, allowSubFeaturePrivileges: false, allowAuditLogging: false, + allowML: false, allowLegacyAuditLogging: false, }); }); @@ -75,6 +77,7 @@ describe('license features', function () { "allowAuditLogging": false, "allowLegacyAuditLogging": false, "allowLogin": false, + "allowML": false, "allowRbac": false, "allowRoleDocumentLevelSecurity": false, "allowRoleFieldLevelSecurity": false, @@ -97,6 +100,7 @@ describe('license features', function () { "allowAuditLogging": true, "allowLegacyAuditLogging": true, "allowLogin": true, + "allowML": true, "allowRbac": true, "allowRoleDocumentLevelSecurity": true, "allowRoleFieldLevelSecurity": true, @@ -134,10 +138,12 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: false, allowAuditLogging: false, + allowML: false, allowLegacyAuditLogging: false, }); - expect(getFeatureSpy).toHaveBeenCalledTimes(1); + expect(getFeatureSpy).toHaveBeenCalledTimes(2); expect(getFeatureSpy).toHaveBeenCalledWith('security'); + expect(getFeatureSpy).toHaveBeenCalledWith('ml'); }); it('should not show login page or other security elements if security is disabled in Elasticsearch.', () => { @@ -160,6 +166,7 @@ describe('license features', function () { allowRbac: false, allowSubFeaturePrivileges: false, allowAuditLogging: false, + allowML: false, allowLegacyAuditLogging: false, }); }); @@ -185,6 +192,7 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: false, allowAuditLogging: false, + allowML: false, allowLegacyAuditLogging: true, }); }); @@ -210,6 +218,7 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: true, allowAuditLogging: true, + allowML: false, allowLegacyAuditLogging: true, }); }); @@ -217,7 +226,10 @@ describe('license features', function () { it('should allow to login, allow RBAC, role mappings, access agreement, sub-feature privileges, and DLS if license >= platinum', () => { const mockRawLicense = licenseMock.createLicense({ license: { mode: 'platinum', type: 'platinum' }, - features: { security: { isEnabled: true, isAvailable: true } }, + features: { + security: { isEnabled: true, isAvailable: true }, + ml: { isEnabled: true, isAvailable: true }, + }, }); const serviceSetup = new SecurityLicenseService().setup({ @@ -235,6 +247,7 @@ describe('license features', function () { allowRbac: true, allowSubFeaturePrivileges: true, allowAuditLogging: true, + allowML: true, allowLegacyAuditLogging: true, }); }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 51093428e84a0..252b91c2f1b0f 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -68,6 +68,15 @@ export class SecurityLicenseService { ); } + private isMLEnabledFromRawLicense(rawLicense: Readonly | undefined) { + if (!rawLicense) { + return false; + } + + const mlFeature = rawLicense.getFeature('ml'); + return mlFeature !== undefined && mlFeature.isAvailable && mlFeature.isEnabled; + } + private calculateFeaturesFromRawLicense( rawLicense: Readonly | undefined ): SecurityLicenseFeatures { @@ -85,6 +94,7 @@ export class SecurityLicenseService { allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, + allowML: false, allowSubFeaturePrivileges: false, layout: rawLicense !== undefined && !rawLicense?.isAvailable @@ -93,6 +103,8 @@ export class SecurityLicenseService { }; } + const allowML = this.isMLEnabledFromRawLicense(rawLicense); + if (!this.isSecurityEnabledFromRawLicense(rawLicense)) { return { showLogin: false, @@ -105,6 +117,7 @@ export class SecurityLicenseService { allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, + allowML, allowSubFeaturePrivileges: false, }; } @@ -124,6 +137,7 @@ export class SecurityLicenseService { // Only platinum and trial licenses are compliant with field- and document-level security. allowRoleDocumentLevelSecurity: isLicensePlatinumOrBetter, allowRoleFieldLevelSecurity: isLicensePlatinumOrBetter, + allowML, allowRbac: true, }; } diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 9839d29291629..325c20a65e176 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -19,7 +19,7 @@ import { SecurityNavControlService } from './nav_control_service'; const validLicense = { isAvailable: true, getFeature: (feature) => { - expect(feature).toEqual('security'); + expect(['security', 'ml']).toContain(feature); return { isAvailable: true, diff --git a/x-pack/plugins/security/server/deprecations/index.ts b/x-pack/plugins/security/server/deprecations/index.ts index 0bbeccf1588b4..d5a1cc52a7187 100644 --- a/x-pack/plugins/security/server/deprecations/index.ts +++ b/x-pack/plugins/security/server/deprecations/index.ts @@ -15,3 +15,4 @@ export { KIBANA_ADMIN_ROLE_NAME, KIBANA_USER_ROLE_NAME, } from './kibana_user_role'; +export { registerMLPrivilegesDeprecation } from './ml_privileges'; diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts new file mode 100644 index 0000000000000..aecf3fa1e9ba2 --- /dev/null +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts @@ -0,0 +1,234 @@ +/* + * 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 { errors } from '@elastic/elasticsearch'; +import type { SecurityGetRoleRole } from '@elastic/elasticsearch/api/types'; + +import type { PackageInfo, RegisterDeprecationsConfig } from 'src/core/server'; +import { + deprecationsServiceMock, + elasticsearchServiceMock, + loggingSystemMock, + savedObjectsClientMock, +} from 'src/core/server/mocks'; + +import { licenseMock } from '../../common/licensing/index.mock'; +import { securityMock } from '../mocks'; +import { registerMLPrivilegesDeprecation } from './ml_privileges'; + +function getDepsMock() { + return { + logger: loggingSystemMock.createLogger(), + deprecationsService: deprecationsServiceMock.createSetupContract(), + license: licenseMock.create({ + allowML: true, + }), + packageInfo: { + branch: 'some-branch', + buildSha: 'sha', + dist: true, + version: '8.0.0', + buildNum: 1, + } as PackageInfo, + applicationName: 'kibana-.kibana', + }; +} + +function getContextMock() { + return { + esClient: elasticsearchServiceMock.createScopedClusterClient(), + savedObjectsClient: savedObjectsClientMock.create(), + }; +} + +function createMockRole(role: Partial = {}) { + return { + name: 'role', + cluster: [], + indices: [], + run_as: [], + applications: [], + metadata: {}, + transient_metadata: { enabled: true }, + ...role, + }; +} + +describe('Machine Learning privileges deprecations', () => { + let mockDeps: ReturnType; + let mockContext: ReturnType; + let deprecationHandler: RegisterDeprecationsConfig; + beforeEach(() => { + mockContext = getContextMock(); + mockDeps = getDepsMock(); + registerMLPrivilegesDeprecation(mockDeps); + + expect(mockDeps.deprecationsService.registerDeprecations).toHaveBeenCalledTimes(1); + deprecationHandler = mockDeps.deprecationsService.registerDeprecations.mock.calls[0][0]; + }); + + it('does not return any deprecations if security is not enabled', async () => { + mockDeps.license.isEnabled.mockReturnValue(false); + + await expect(deprecationHandler.getDeprecations(mockContext)).resolves.toEqual([]); + expect(mockContext.esClient.asCurrentUser.security.getRole).not.toHaveBeenCalled(); + }); + + it('does not return any deprecations if ML is not enabled', async () => { + mockDeps.license.isEnabled.mockReturnValue(true); + mockDeps.license.getFeatures.mockReturnValue({ + allowRbac: true, + showLogin: true, + allowLogin: true, + showLinks: true, + showRoleMappingsManagement: true, + allowAccessAgreement: true, + allowAuditLogging: true, + allowLegacyAuditLogging: true, + allowSubFeaturePrivileges: true, + allowRoleDocumentLevelSecurity: true, + allowRoleFieldLevelSecurity: true, + allowML: false, + }); + + await expect(deprecationHandler.getDeprecations(mockContext)).resolves.toEqual([]); + expect(mockContext.esClient.asCurrentUser.security.getRole).not.toHaveBeenCalled(); + }); + + it('does not return any deprecations if none of the custom roles grant base privileges', async () => { + mockContext.esClient.asCurrentUser.security.getRole.mockResolvedValue( + securityMock.createApiResponse({ body: { roleA: createMockRole() } }) + ); + + mockContext.esClient.asCurrentUser.security.getRoleMapping.mockResolvedValue( + securityMock.createApiResponse({ + body: { + mappingA: { enabled: true, roles: ['roleA'], rules: {}, metadata: {} }, + }, + }) + ); + + await expect(deprecationHandler.getDeprecations(mockContext)).resolves.toEqual([]); + }); + + it('returns deprecations even if cannot retrieve roles due to permission error', async () => { + mockContext.esClient.asCurrentUser.security.getRole.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 403, body: {} })) + ); + + await expect(deprecationHandler.getDeprecations(mockContext)).resolves.toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Make sure you have a \\"manage_security\\" cluster privilege assigned.", + ], + }, + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/some-branch/xpack-security.html#_required_permissions_7", + "level": "fetch_error", + "message": "You do not have enough permissions to fix this deprecation.", + "title": "Access to Machine Learning features will be granted in 8.0", + }, + ] + `); + }); + + it('returns deprecations even if cannot retrieve roles due to unknown error', async () => { + mockContext.esClient.asCurrentUser.security.getRole.mockRejectedValue( + new errors.ResponseError(securityMock.createApiResponse({ statusCode: 500, body: {} })) + ); + + await expect(deprecationHandler.getDeprecations(mockContext)).resolves.toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Check Kibana logs for more details.", + ], + }, + "deprecationType": "feature", + "level": "fetch_error", + "message": "Failed to perform deprecation check. Check Kibana logs for more details.", + "title": "Access to Machine Learning features will be granted in 8.0", + }, + ] + `); + }); + + it('returns role-related deprecations', async () => { + mockContext.esClient.asCurrentUser.security.getRole.mockResolvedValue( + securityMock.createApiResponse({ + body: { + roleA: createMockRole({ + applications: [ + { + application: 'kibana-.kibana', + privileges: ['all'], + resources: ['*'], + }, + ], + }), + roleB: createMockRole({ + applications: [ + { + application: 'kibana-.kibana', + privileges: ['space_all'], + resources: ['space:b'], + }, + ], + }), + roleC: createMockRole({ + applications: [ + { + application: 'kibana-.kibana', + privileges: ['space_read'], + resources: ['space:b'], + }, + ], + }), + roleD: createMockRole({ + applications: [ + { + // This shouldn't trigger a deprecation because of a mismatched applicaiton name + application: 'NOT_kibana-.kibana', + privileges: ['space_read'], + resources: ['space:b'], + }, + ], + }), + roleE: createMockRole({ + applications: [ + { + // This shouldn't trigger a deprecation because feature privileges are granted instead + application: 'kibana-.kibana', + privileges: ['feature_discover.all'], + resources: ['*'], + }, + ], + }), + }, + }) + ); + + await expect(deprecationHandler.getDeprecations(mockContext)).resolves.toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "The following roles will grant access to Machine Learning features starting in 8.0. Update this role to grant access to specific features if you do not want to grant access to Machine Learning: roleA, roleB, roleC", + ], + }, + "deprecationType": "feature", + "level": "warning", + "message": "Roles that grant \\"all\\" or \\"read\\" privileges to all features will include Machine Learning.", + "title": "Access to Machine Learning features will be granted in 8.0", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.ts new file mode 100644 index 0000000000000..6f7e861cc281d --- /dev/null +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.ts @@ -0,0 +1,186 @@ +/* + * 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 type { + DeprecationsDetails, + DeprecationsServiceSetup, + ElasticsearchClient, + Logger, + PackageInfo, +} from 'src/core/server'; + +import type { SecurityLicense } from '../../common'; +import type { Role } from '../../common/model'; +import { isRoleReserved } from '../../common/model'; +import { transformElasticsearchRoleToRole } from '../authorization'; +import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; + +export const KIBANA_USER_ROLE_NAME = 'kibana_user'; +export const KIBANA_ADMIN_ROLE_NAME = 'kibana_admin'; + +interface Deps { + deprecationsService: DeprecationsServiceSetup; + license: SecurityLicense; + logger: Logger; + packageInfo: PackageInfo; + applicationName: string; +} + +function getDeprecationTitle() { + return i18n.translate('xpack.security.deprecations.kibanaUser.deprecationTitle', { + defaultMessage: 'Access to Machine Learning features will be granted in 8.0', + values: { userRoleName: KIBANA_USER_ROLE_NAME }, + }); +} + +function getDeprecationMessage() { + return i18n.translate('xpack.security.deprecations.kibanaUser.deprecationMessage', { + defaultMessage: + 'Roles that grant "all" or "read" privileges to all features will include Machine Learning.', + }); +} + +export const registerMLPrivilegesDeprecation = ({ + deprecationsService, + logger, + license, + packageInfo, + applicationName, +}: Deps) => { + deprecationsService.registerDeprecations({ + getDeprecations: async (context) => { + // Nothing to do if security or ml is disabled + if (!license.isEnabled() || !license.getFeatures().allowML) { + return []; + } + + return [ + ...(await getRolesDeprecations( + context.esClient.asCurrentUser, + logger, + packageInfo, + applicationName + )), + ]; + }, + }); +}; + +async function getRolesDeprecations( + client: ElasticsearchClient, + logger: Logger, + packageInfo: PackageInfo, + applicationName: string +): Promise { + let roles: Role[]; + try { + const elasticsearchRoles = (await client.security.getRole()).body; + + roles = Object.entries(elasticsearchRoles).map(([roleName, elasticsearchRole]) => + transformElasticsearchRoleToRole( + // @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]` + elasticsearchRole, + roleName, + applicationName + ) + ); + } catch (err) { + if (getErrorStatusCode(err) === 403) { + logger.warn( + `Failed to retrieve roles when checking for deprecations: the "manage_security" cluster privilege is required.` + ); + } else { + logger.error( + `Failed to retrieve roles when checking for deprecations, unexpected error: ${getDetailedErrorMessage( + err + )}.` + ); + } + return deprecationError(packageInfo, err); + } + + const rolesWithBasePrivileges = roles + .filter((role) => { + const hasBasePrivileges = role.kibana.some( + (kp) => kp.base.includes('all') || kp.base.includes('read') + ); + return !isRoleReserved(role) && hasBasePrivileges; + }) + .map((role) => role.name); + + if (rolesWithBasePrivileges.length === 0) { + return []; + } + + return [ + { + title: getDeprecationTitle(), + message: getDeprecationMessage(), + level: 'warning', + deprecationType: 'feature', + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationCorrectiveAction', { + defaultMessage: + 'The following roles will grant access to Machine Learning features starting in 8.0. Update this role to grant access to specific features if you do not want to grant access to Machine Learning: {roles}', + values: { + roles: rolesWithBasePrivileges.join(', '), + }, + }), + ], + }, + }, + ]; +} + +function deprecationError(packageInfo: PackageInfo, error: Error): DeprecationsDetails[] { + const title = getDeprecationTitle(); + + if (getErrorStatusCode(error) === 403) { + return [ + { + title, + level: 'fetch_error', + deprecationType: 'feature', + message: i18n.translate('xpack.security.deprecations.mlPrivileges.forbiddenErrorMessage', { + defaultMessage: 'You do not have enough permissions to fix this deprecation.', + }), + documentationUrl: `https://www.elastic.co/guide/en/kibana/${packageInfo.branch}/xpack-security.html#_required_permissions_7`, + correctiveActions: { + manualSteps: [ + i18n.translate( + 'xpack.security.deprecations.mlPrivileges.forbiddenErrorCorrectiveAction', + { + defaultMessage: + 'Make sure you have a "manage_security" cluster privilege assigned.', + } + ), + ], + }, + }, + ]; + } + + return [ + { + title, + level: 'fetch_error', + deprecationType: 'feature', + message: i18n.translate('xpack.security.deprecations.mlPrivileges.unknownErrorMessage', { + defaultMessage: 'Failed to perform deprecation check. Check Kibana logs for more details.', + }), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.security.deprecations.mlPrivileges.unknownErrorCorrectiveAction', { + defaultMessage: 'Check Kibana logs for more details.', + }), + ], + }, + }, + ]; +} diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index bf540d5d4ddc8..ffe8793448a74 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -46,6 +46,7 @@ import { getPrivilegeDeprecationsService, registerKibanaDashboardOnlyRoleDeprecation, registerKibanaUserRoleDeprecation, + registerMLPrivilegesDeprecation, } from './deprecations'; import { ElasticsearchService } from './elasticsearch'; import type { SecurityFeatureUsageServiceStart } from './feature_usage'; @@ -434,5 +435,12 @@ export class SecurityPlugin logger, packageInfo: this.initializerContext.env.packageInfo, }); + registerMLPrivilegesDeprecation({ + deprecationsService: core.deprecations, + license, + logger, + packageInfo: this.initializerContext.env.packageInfo, + applicationName: this.authorizationSetup?.applicationName!, + }); } } From 68f48b085b633cad4dd017fae38e6e496c6651b9 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 18 Oct 2021 15:44:06 -0400 Subject: [PATCH 2/8] fix i18n ids --- x-pack/plugins/security/server/deprecations/ml_privileges.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.ts index 6f7e861cc281d..9df1b86b46dea 100644 --- a/x-pack/plugins/security/server/deprecations/ml_privileges.ts +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.ts @@ -32,14 +32,13 @@ interface Deps { } function getDeprecationTitle() { - return i18n.translate('xpack.security.deprecations.kibanaUser.deprecationTitle', { + return i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationTitle', { defaultMessage: 'Access to Machine Learning features will be granted in 8.0', - values: { userRoleName: KIBANA_USER_ROLE_NAME }, }); } function getDeprecationMessage() { - return i18n.translate('xpack.security.deprecations.kibanaUser.deprecationMessage', { + return i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationMessage', { defaultMessage: 'Roles that grant "all" or "read" privileges to all features will include Machine Learning.', }); From d3de445a0fce22eccd0123639205ddd56ca0a195 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 18 Oct 2021 16:06:02 -0400 Subject: [PATCH 3/8] refactor to use privilege_deprecations service --- .../security/common/model/deprecations.ts | 12 ++-- x-pack/plugins/security/common/model/index.ts | 4 +- .../server/deprecations/ml_privileges.test.ts | 15 ++--- .../server/deprecations/ml_privileges.ts | 65 +++++++------------ .../privilege_deprecations.test.ts | 18 ++--- .../deprecations/privilege_deprecations.ts | 20 +++--- x-pack/plugins/security/server/mocks.ts | 2 +- x-pack/plugins/security/server/plugin.test.ts | 2 +- .../deprecation_privileges/index.test.ts | 18 ++--- .../server/deprecation_privileges/index.ts | 8 +-- .../security_solution/server/plugin.ts | 3 +- 11 files changed, 72 insertions(+), 95 deletions(-) diff --git a/x-pack/plugins/security/common/model/deprecations.ts b/x-pack/plugins/security/common/model/deprecations.ts index e990f370c5173..5b12f2c41f8ff 100644 --- a/x-pack/plugins/security/common/model/deprecations.ts +++ b/x-pack/plugins/security/common/model/deprecations.ts @@ -9,17 +9,17 @@ import type { DeprecationsDetails, GetDeprecationsContext } from '../../../../../src/core/server'; import type { Role } from './role'; -export interface PrivilegeDeprecationsRolesByFeatureIdResponse { +export interface PrivilegeDeprecationsRolesResponse { roles?: Role[]; errors?: DeprecationsDetails[]; } -export interface PrivilegeDeprecationsRolesByFeatureIdRequest { +export interface PrivilegeDeprecationsRolesRequest { context: GetDeprecationsContext; - featureId: string; + featureId?: string; } export interface PrivilegeDeprecationsService { - getKibanaRolesByFeatureId: ( - args: PrivilegeDeprecationsRolesByFeatureIdRequest - ) => Promise; + getKibanaRoles: ( + args: PrivilegeDeprecationsRolesRequest + ) => Promise; } diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 082e6bdc12cd0..1d351da114cf9 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -34,7 +34,7 @@ export { RoleMapping, } from './role_mapping'; export { - PrivilegeDeprecationsRolesByFeatureIdRequest, - PrivilegeDeprecationsRolesByFeatureIdResponse, + PrivilegeDeprecationsRolesRequest, + PrivilegeDeprecationsRolesResponse, PrivilegeDeprecationsService, } from './deprecations'; diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts index aecf3fa1e9ba2..21fab9b176f46 100644 --- a/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts @@ -125,14 +125,12 @@ describe('Machine Learning privileges deprecations', () => { Object { "correctiveActions": Object { "manualSteps": Array [ - "Make sure you have a \\"manage_security\\" cluster privilege assigned.", + "A user with the \\"manage_security\\" cluster privilege is required to perform this check.", ], }, - "deprecationType": "feature", - "documentationUrl": "https://www.elastic.co/guide/en/kibana/some-branch/xpack-security.html#_required_permissions_7", "level": "fetch_error", - "message": "You do not have enough permissions to fix this deprecation.", - "title": "Access to Machine Learning features will be granted in 8.0", + "message": "You must have the 'manage_security' cluster privilege to fix role deprecations.", + "title": "Error in privilege deprecations services", }, ] `); @@ -148,13 +146,12 @@ describe('Machine Learning privileges deprecations', () => { Object { "correctiveActions": Object { "manualSteps": Array [ - "Check Kibana logs for more details.", + "A user with the \\"manage_security\\" cluster privilege is required to perform this check.", ], }, - "deprecationType": "feature", "level": "fetch_error", - "message": "Failed to perform deprecation check. Check Kibana logs for more details.", - "title": "Access to Machine Learning features will be granted in 8.0", + "message": "Error retrieving roles for privilege deprecations: {}", + "title": "Error in privilege deprecations services", }, ] `); diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.ts index 9df1b86b46dea..a9fbe3dfd2647 100644 --- a/x-pack/plugins/security/server/deprecations/ml_privileges.ts +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.ts @@ -9,16 +9,19 @@ import { i18n } from '@kbn/i18n'; import type { DeprecationsDetails, DeprecationsServiceSetup, - ElasticsearchClient, + GetDeprecationsContext, Logger, PackageInfo, } from 'src/core/server'; import type { SecurityLicense } from '../../common'; -import type { Role } from '../../common/model'; +import type { + PrivilegeDeprecationsRolesResponse, + PrivilegeDeprecationsService, +} from '../../common/model'; import { isRoleReserved } from '../../common/model'; -import { transformElasticsearchRoleToRole } from '../authorization'; -import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; +import { getErrorStatusCode } from '../errors'; +import { getPrivilegeDeprecationsService } from './privilege_deprecations'; export const KIBANA_USER_ROLE_NAME = 'kibana_user'; export const KIBANA_ADMIN_ROLE_NAME = 'kibana_admin'; @@ -58,52 +61,30 @@ export const registerMLPrivilegesDeprecation = ({ return []; } - return [ - ...(await getRolesDeprecations( - context.esClient.asCurrentUser, - logger, - packageInfo, - applicationName - )), - ]; + const privilegeDeprecationService = getPrivilegeDeprecationsService( + { + applicationName, + }, + license, + logger + ); + + return [...(await getRolesDeprecations(context, privilegeDeprecationService))]; }, }); }; async function getRolesDeprecations( - client: ElasticsearchClient, - logger: Logger, - packageInfo: PackageInfo, - applicationName: string + context: GetDeprecationsContext, + privilegeDeprecationService: PrivilegeDeprecationsService ): Promise { - let roles: Role[]; - try { - const elasticsearchRoles = (await client.security.getRole()).body; - - roles = Object.entries(elasticsearchRoles).map(([roleName, elasticsearchRole]) => - transformElasticsearchRoleToRole( - // @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]` - elasticsearchRole, - roleName, - applicationName - ) - ); - } catch (err) { - if (getErrorStatusCode(err) === 403) { - logger.warn( - `Failed to retrieve roles when checking for deprecations: the "manage_security" cluster privilege is required.` - ); - } else { - logger.error( - `Failed to retrieve roles when checking for deprecations, unexpected error: ${getDetailedErrorMessage( - err - )}.` - ); - } - return deprecationError(packageInfo, err); + const response: PrivilegeDeprecationsRolesResponse = + await privilegeDeprecationService.getKibanaRoles({ context }); + if (response.errors) { + return response.errors; } - const rolesWithBasePrivileges = roles + const rolesWithBasePrivileges = (response.roles ?? []) .filter((role) => { const hasBasePrivileges = role.kibana.some( (kp) => kp.base.includes('all') || kp.base.includes('read') diff --git a/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts b/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts index e889eb17d5af9..f8cb2d7bf9e9a 100644 --- a/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts +++ b/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts @@ -15,17 +15,13 @@ const kibanaIndexName = '.a-kibana-index'; const application = `kibana-${kibanaIndexName}`; describe('#getPrivilegeDeprecationsService', () => { - describe('#getKibanaRolesByFeatureId', () => { + describe('#getKibanaRoles', () => { const mockAsCurrentUser = elasticsearchServiceMock.createScopedClusterClient(); const mockLicense = licenseMock.create(); const mockLogger = loggingSystemMock.createLogger(); const authz = { applicationName: application }; - const { getKibanaRolesByFeatureId } = getPrivilegeDeprecationsService( - authz, - mockLicense, - mockLogger - ); + const { getKibanaRoles } = getPrivilegeDeprecationsService(authz, mockLicense, mockLogger); it('happy path to find siem roles with feature_siem privileges', async () => { mockAsCurrentUser.asCurrentUser.security.getRole.mockResolvedValue( @@ -56,7 +52,7 @@ describe('#getPrivilegeDeprecationsService', () => { savedObjectsClient: jest.fn(), } as unknown as GetDeprecationsContext; - const resp = await getKibanaRolesByFeatureId({ context: mockContext, featureId: 'siem' }); + const resp = await getKibanaRoles({ context: mockContext, featureId: 'siem' }); expect(resp).toMatchInlineSnapshot(` Object { "roles": Array [ @@ -130,7 +126,7 @@ describe('#getPrivilegeDeprecationsService', () => { savedObjectsClient: jest.fn(), } as unknown as GetDeprecationsContext; - const resp = await getKibanaRolesByFeatureId({ context: mockContext, featureId: 'siem' }); + const resp = await getKibanaRoles({ context: mockContext, featureId: 'siem' }); expect(resp).toMatchInlineSnapshot(` Object { "roles": Array [ @@ -209,7 +205,7 @@ describe('#getPrivilegeDeprecationsService', () => { savedObjectsClient: jest.fn(), } as unknown as GetDeprecationsContext; - const resp = await getKibanaRolesByFeatureId({ context: mockContext, featureId: 'siem' }); + const resp = await getKibanaRoles({ context: mockContext, featureId: 'siem' }); expect(resp).toMatchInlineSnapshot(` Object { "roles": Array [], @@ -230,7 +226,7 @@ describe('#getPrivilegeDeprecationsService', () => { savedObjectsClient: jest.fn(), } as unknown as GetDeprecationsContext; - const resp = await getKibanaRolesByFeatureId({ context: mockContext, featureId: 'siem' }); + const resp = await getKibanaRoles({ context: mockContext, featureId: 'siem' }); expect(resp).toMatchInlineSnapshot(` Object { "errors": Array [ @@ -262,7 +258,7 @@ describe('#getPrivilegeDeprecationsService', () => { savedObjectsClient: jest.fn(), } as unknown as GetDeprecationsContext; - const resp = await getKibanaRolesByFeatureId({ context: mockContext, featureId: 'siem' }); + const resp = await getKibanaRoles({ context: mockContext, featureId: 'siem' }); expect(resp).toMatchInlineSnapshot(` Object { "errors": Array [ diff --git a/x-pack/plugins/security/server/deprecations/privilege_deprecations.ts b/x-pack/plugins/security/server/deprecations/privilege_deprecations.ts index df212d5c7bde3..7a35a0099e7ac 100644 --- a/x-pack/plugins/security/server/deprecations/privilege_deprecations.ts +++ b/x-pack/plugins/security/server/deprecations/privilege_deprecations.ts @@ -10,8 +10,8 @@ import type { Logger } from 'src/core/server'; import type { SecurityLicense } from '../../common/licensing'; import type { - PrivilegeDeprecationsRolesByFeatureIdRequest, - PrivilegeDeprecationsRolesByFeatureIdResponse, + PrivilegeDeprecationsRolesRequest, + PrivilegeDeprecationsRolesResponse, } from '../../common/model'; import { transformElasticsearchRoleToRole } from '../authorization'; import type { AuthorizationServiceSetupInternal, ElasticsearchRole } from '../authorization'; @@ -22,10 +22,10 @@ export const getPrivilegeDeprecationsService = ( license: SecurityLicense, logger: Logger ) => { - const getKibanaRolesByFeatureId = async ({ + const getKibanaRoles = async ({ context, featureId, - }: PrivilegeDeprecationsRolesByFeatureIdRequest): Promise => { + }: PrivilegeDeprecationsRolesRequest): Promise => { // Nothing to do if security is disabled if (!license.isEnabled()) { return { @@ -95,12 +95,16 @@ export const getPrivilegeDeprecationsService = ( }; } return { - roles: kibanaRoles.filter((role) => - role.kibana.find((privilege) => Object.hasOwnProperty.call(privilege.feature, featureId)) - ), + roles: featureId + ? kibanaRoles.filter((role) => + role.kibana.find((privilege) => + Object.hasOwnProperty.call(privilege.feature, featureId) + ) + ) + : kibanaRoles, }; }; return Object.freeze({ - getKibanaRolesByFeatureId, + getKibanaRoles, }); }; diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index 7cae0d29bf943..5869ae67c1faa 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -29,7 +29,7 @@ function createSetupMock() { registerSpacesService: jest.fn(), license: licenseMock.create(), privilegeDeprecationsService: { - getKibanaRolesByFeatureId: jest.fn(), + getKibanaRoles: jest.fn(), }, }; } diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 4784e14a11fb4..596fddd0c416f 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -124,7 +124,7 @@ describe('Security Plugin', () => { "isLicenseAvailable": [Function], }, "privilegeDeprecationsService": Object { - "getKibanaRolesByFeatureId": [Function], + "getKibanaRoles": [Function], }, } `); 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 213beb6207184..aeee6f15a950f 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 @@ -324,7 +324,7 @@ describe('deprecations', () => { savedObjectsClient: jest.fn(), } as unknown as GetDeprecationsContext; const getDeprecations = jest.fn(); - const getKibanaRolesByFeatureId = jest.fn(); + const getKibanaRoles = jest.fn(); const mockDeprecationsService: DeprecationsServiceSetup = { registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => { getDeprecations.mockImplementation(deprecationContext.getDeprecations); @@ -334,15 +334,15 @@ describe('deprecations', () => { beforeAll(() => { registerPrivilegeDeprecations({ deprecationsService: mockDeprecationsService, - getKibanaRolesByFeatureId, + getKibanaRoles, logger: loggingSystemMock.createLogger(), }); }); beforeEach(() => { - getKibanaRolesByFeatureId.mockReset(); + getKibanaRoles.mockReset(); }); - test('getDeprecations return the errors from getKibanaRolesByFeatureId', async () => { + test('getDeprecations return the errors from getKibanaRoles', async () => { const errorResponse = { errors: [ { @@ -357,13 +357,13 @@ describe('deprecations', () => { }, ], }; - getKibanaRolesByFeatureId.mockResolvedValue(errorResponse); + getKibanaRoles.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({ + getKibanaRoles.mockResolvedValue({ roles: [ { _transform_error: [], @@ -399,7 +399,7 @@ describe('deprecations', () => { }); test('happy path build securitySolutionCases privileges from siem privileges', async () => { - getKibanaRolesByFeatureId.mockResolvedValue({ + getKibanaRoles.mockResolvedValue({ roles: [ { _transform_error: [], @@ -481,7 +481,7 @@ describe('deprecations', () => { }); test('getDeprecations handles multiple roles and filters out any that have already been updated', async () => { - getKibanaRolesByFeatureId.mockResolvedValue({ + getKibanaRoles.mockResolvedValue({ roles: [ { _transform_error: [], @@ -616,7 +616,7 @@ describe('deprecations', () => { }); test('getDeprecations handles multiple roles and filters out any that do not grant access to Cases', async () => { - getKibanaRolesByFeatureId.mockResolvedValue({ + getKibanaRoles.mockResolvedValue({ roles: [ { _transform_error: [], 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 b56583d26261f..af2d46851affc 100644 --- a/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts +++ b/x-pack/plugins/security_solution/server/deprecation_privileges/index.ts @@ -14,7 +14,7 @@ import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../common/constants'; interface Deps { deprecationsService: DeprecationsServiceSetup; - getKibanaRolesByFeatureId?: PrivilegeDeprecationsService['getKibanaRolesByFeatureId']; + getKibanaRoles?: PrivilegeDeprecationsService['getKibanaRoles']; logger: Logger; } @@ -70,16 +70,16 @@ function outdatedSiemRolePredicate(role: Role) { export const registerPrivilegeDeprecations = ({ deprecationsService, - getKibanaRolesByFeatureId, + getKibanaRoles, logger, }: Deps) => { deprecationsService.registerDeprecations({ getDeprecations: async (context) => { let deprecatedRoles: DeprecationsDetails[] = []; - if (!getKibanaRolesByFeatureId) { + if (!getKibanaRoles) { return deprecatedRoles; } - const responseRoles = await getKibanaRolesByFeatureId({ + const responseRoles = await getKibanaRoles({ context, featureId: 'siem', }); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a6ccc0f5569fb..7326a797a0aff 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -393,8 +393,7 @@ export class Plugin implements IPlugin Date: Mon, 18 Oct 2021 16:07:11 -0400 Subject: [PATCH 4/8] fix type check --- x-pack/plugins/security/server/routes/views/login.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index 6def1b7d77df3..ab6d8f6fdf8a9 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -172,6 +172,7 @@ describe('Login view routes', () => { allowSubFeaturePrivileges: true, allowAuditLogging: true, allowLegacyAuditLogging: true, + allowML: true, showLogin: true, }); From 8fe612946355a8f59905275454e7297e0da040a3 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 18 Oct 2021 16:22:27 -0400 Subject: [PATCH 5/8] remove unused function --- .../server/deprecations/ml_privileges.ts | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.ts index a9fbe3dfd2647..7ac9363815a9f 100644 --- a/x-pack/plugins/security/server/deprecations/ml_privileges.ts +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.ts @@ -20,7 +20,6 @@ import type { PrivilegeDeprecationsService, } from '../../common/model'; import { isRoleReserved } from '../../common/model'; -import { getErrorStatusCode } from '../errors'; import { getPrivilegeDeprecationsService } from './privilege_deprecations'; export const KIBANA_USER_ROLE_NAME = 'kibana_user'; @@ -117,50 +116,3 @@ async function getRolesDeprecations( }, ]; } - -function deprecationError(packageInfo: PackageInfo, error: Error): DeprecationsDetails[] { - const title = getDeprecationTitle(); - - if (getErrorStatusCode(error) === 403) { - return [ - { - title, - level: 'fetch_error', - deprecationType: 'feature', - message: i18n.translate('xpack.security.deprecations.mlPrivileges.forbiddenErrorMessage', { - defaultMessage: 'You do not have enough permissions to fix this deprecation.', - }), - documentationUrl: `https://www.elastic.co/guide/en/kibana/${packageInfo.branch}/xpack-security.html#_required_permissions_7`, - correctiveActions: { - manualSteps: [ - i18n.translate( - 'xpack.security.deprecations.mlPrivileges.forbiddenErrorCorrectiveAction', - { - defaultMessage: - 'Make sure you have a "manage_security" cluster privilege assigned.', - } - ), - ], - }, - }, - ]; - } - - return [ - { - title, - level: 'fetch_error', - deprecationType: 'feature', - message: i18n.translate('xpack.security.deprecations.mlPrivileges.unknownErrorMessage', { - defaultMessage: 'Failed to perform deprecation check. Check Kibana logs for more details.', - }), - correctiveActions: { - manualSteps: [ - i18n.translate('xpack.security.deprecations.mlPrivileges.unknownErrorCorrectiveAction', { - defaultMessage: 'Check Kibana logs for more details.', - }), - ], - }, - }, - ]; -} From a6a41a99222695ec2369c487c5a10bbc4ab60440 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 18 Oct 2021 20:17:27 -0400 Subject: [PATCH 6/8] Adding additional test case --- .../privilege_deprecations.test.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts b/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts index f8cb2d7bf9e9a..f795f3e0a46d5 100644 --- a/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts +++ b/x-pack/plugins/security/server/deprecations/privilege_deprecations.test.ts @@ -23,6 +23,63 @@ describe('#getPrivilegeDeprecationsService', () => { const { getKibanaRoles } = getPrivilegeDeprecationsService(authz, mockLicense, mockLogger); + it('returns all roles when the "feature" parameter is not provided', async () => { + mockAsCurrentUser.asCurrentUser.security.getRole.mockResolvedValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['feature_siem.all', 'feature_siem.cases_read'], + resources: ['space:securitySolutions'], + }, + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + second_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['all'], + resources: ['*'], + }, + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }) + ); + + const mockContext = { + esClient: mockAsCurrentUser, + savedObjectsClient: jest.fn(), + } as unknown as GetDeprecationsContext; + + const resp = await getKibanaRoles({ context: mockContext }); + expect(resp).not.toHaveProperty('errors'); + expect(resp.roles?.map((r) => r.name)).toMatchInlineSnapshot(` + Array [ + "first_role", + "second_role", + ] + `); + }); + it('happy path to find siem roles with feature_siem privileges', async () => { mockAsCurrentUser.asCurrentUser.security.getRole.mockResolvedValue( elasticsearchServiceMock.createSuccessTransportRequestPromise({ From 2f9524b49904f44d7ec78170aacbbc7e3b4f9c92 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 19 Oct 2021 15:12:55 -0400 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- .../server/deprecations/ml_privileges.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.ts index 7ac9363815a9f..5272f4e5e425b 100644 --- a/x-pack/plugins/security/server/deprecations/ml_privileges.ts +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.ts @@ -22,9 +22,6 @@ import type { import { isRoleReserved } from '../../common/model'; import { getPrivilegeDeprecationsService } from './privilege_deprecations'; -export const KIBANA_USER_ROLE_NAME = 'kibana_user'; -export const KIBANA_ADMIN_ROLE_NAME = 'kibana_admin'; - interface Deps { deprecationsService: DeprecationsServiceSetup; license: SecurityLicense; @@ -35,14 +32,14 @@ interface Deps { function getDeprecationTitle() { return i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationTitle', { - defaultMessage: 'Access to Machine Learning features will be granted in 8.0', + defaultMessage: 'The Machine Learning feature is changing', }); } function getDeprecationMessage() { return i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationMessage', { defaultMessage: - 'Roles that grant "all" or "read" privileges to all features will include Machine Learning.', + 'Roles that use base privileges will include the Machine Learning feature in 8.0.', }); } @@ -68,14 +65,15 @@ export const registerMLPrivilegesDeprecation = ({ logger ); - return [...(await getRolesDeprecations(context, privilegeDeprecationService))]; + return [...(await getRolesDeprecations(context, privilegeDeprecationService, packageInfo))]; }, }); }; async function getRolesDeprecations( context: GetDeprecationsContext, - privilegeDeprecationService: PrivilegeDeprecationsService + privilegeDeprecationService: PrivilegeDeprecationsService, + packageInfo: PackageInfo ): Promise { const response: PrivilegeDeprecationsRolesResponse = await privilegeDeprecationService.getKibanaRoles({ context }); @@ -102,11 +100,19 @@ async function getRolesDeprecations( message: getDeprecationMessage(), level: 'warning', deprecationType: 'feature', + documentationUrl: `https://www.elastic.co/guide/en/kibana/${packageInfo.branch}/kibana-privileges.html`, correctiveActions: { manualSteps: [ - i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationCorrectiveAction', { + i18n.translate('xpack.security.deprecations.mlPrivileges.manualSteps1', { + defaultMessage: + 'Change the affected roles to use feature privileges that grant access to only the desired features instead.', + }), + i18n.translate('xpack.security.deprecations.mlPrivileges.manualSteps2', { defaultMessage: - 'The following roles will grant access to Machine Learning features starting in 8.0. Update this role to grant access to specific features if you do not want to grant access to Machine Learning: {roles}', + "If you don't make any changes, affected roles will grant access to the Machine Learning feature in 8.0.", + }), + i18n.translate('xpack.security.deprecations.mlPrivileges.manualSteps3', { + defaultMessage: 'The affected roles are: {roles}', values: { roles: rolesWithBasePrivileges.join(', '), }, From fea10e9bf1aad724d281f27ee9f997c4133789ec Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 19 Oct 2021 15:15:06 -0400 Subject: [PATCH 8/8] update tests --- .../security/server/deprecations/ml_privileges.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts b/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts index 21fab9b176f46..e8dfcb40a80e8 100644 --- a/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.test.ts @@ -217,13 +217,16 @@ describe('Machine Learning privileges deprecations', () => { Object { "correctiveActions": Object { "manualSteps": Array [ - "The following roles will grant access to Machine Learning features starting in 8.0. Update this role to grant access to specific features if you do not want to grant access to Machine Learning: roleA, roleB, roleC", + "Change the affected roles to use feature privileges that grant access to only the desired features instead.", + "If you don't make any changes, affected roles will grant access to the Machine Learning feature in 8.0.", + "The affected roles are: roleA, roleB, roleC", ], }, "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/some-branch/kibana-privileges.html", "level": "warning", - "message": "Roles that grant \\"all\\" or \\"read\\" privileges to all features will include Machine Learning.", - "title": "Access to Machine Learning features will be granted in 8.0", + "message": "Roles that use base privileges will include the Machine Learning feature in 8.0.", + "title": "The Machine Learning feature is changing", }, ] `);