From 6f3400379b89caa0c6cbac8c021e7fd7f1816a2d Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Wed, 20 Oct 2021 15:16:19 -0400 Subject: [PATCH] [7.x] - Deprecate excluding ML from base privileges (#115445) Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/licensing/license_features.ts | 5 + .../common/licensing/license_service.test.ts | 17 +- .../common/licensing/license_service.ts | 14 ++ .../security/common/model/deprecations.ts | 12 +- x-pack/plugins/security/common/model/index.ts | 4 +- .../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 | 124 ++++++++++ .../privilege_deprecations.test.ts | 75 +++++- .../deprecations/privilege_deprecations.ts | 20 +- x-pack/plugins/security/server/mocks.ts | 2 +- x-pack/plugins/security/server/plugin.test.ts | 2 +- x-pack/plugins/security/server/plugin.ts | 8 + .../server/routes/views/login.test.ts | 1 + .../deprecation_privileges/index.test.ts | 18 +- .../server/deprecation_privileges/index.ts | 8 +- .../security_solution/server/plugin.ts | 3 +- 18 files changed, 503 insertions(+), 47 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 ac80c89ae7be39..edd6a7ea2084bc 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 cdc80c1a038f15..5d7ce321f9ef6f 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 51093428e84a03..252b91c2f1b0f5 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/common/model/deprecations.ts b/x-pack/plugins/security/common/model/deprecations.ts index e990f370c51736..5b12f2c41f8ff9 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 082e6bdc12cd07..1d351da114cf9c 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/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 9839d29291629a..325c20a65e1766 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 0bbeccf1588b43..d5a1cc52a71872 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 00000000000000..e8dfcb40a80e8d --- /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 [ + "A user with the \\"manage_security\\" cluster privilege is required to perform this check.", + ], + }, + "level": "fetch_error", + "message": "You must have the 'manage_security' cluster privilege to fix role deprecations.", + "title": "Error in privilege deprecations services", + }, + ] + `); + }); + + 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 [ + "A user with the \\"manage_security\\" cluster privilege is required to perform this check.", + ], + }, + "level": "fetch_error", + "message": "Error retrieving roles for privilege deprecations: {}", + "title": "Error in privilege deprecations services", + }, + ] + `); + }); + + 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 [ + "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 use base privileges will include the Machine Learning feature in 8.0.", + "title": "The Machine Learning feature is changing", + }, + ] + `); + }); +}); 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 00000000000000..5272f4e5e425b0 --- /dev/null +++ b/x-pack/plugins/security/server/deprecations/ml_privileges.ts @@ -0,0 +1,124 @@ +/* + * 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, + GetDeprecationsContext, + Logger, + PackageInfo, +} from 'src/core/server'; + +import type { SecurityLicense } from '../../common'; +import type { + PrivilegeDeprecationsRolesResponse, + PrivilegeDeprecationsService, +} from '../../common/model'; +import { isRoleReserved } from '../../common/model'; +import { getPrivilegeDeprecationsService } from './privilege_deprecations'; + +interface Deps { + deprecationsService: DeprecationsServiceSetup; + license: SecurityLicense; + logger: Logger; + packageInfo: PackageInfo; + applicationName: string; +} + +function getDeprecationTitle() { + return i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationTitle', { + defaultMessage: 'The Machine Learning feature is changing', + }); +} + +function getDeprecationMessage() { + return i18n.translate('xpack.security.deprecations.mlPrivileges.deprecationMessage', { + defaultMessage: + 'Roles that use base privileges will include the Machine Learning feature in 8.0.', + }); +} + +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 []; + } + + const privilegeDeprecationService = getPrivilegeDeprecationsService( + { + applicationName, + }, + license, + logger + ); + + return [...(await getRolesDeprecations(context, privilegeDeprecationService, packageInfo))]; + }, + }); +}; + +async function getRolesDeprecations( + context: GetDeprecationsContext, + privilegeDeprecationService: PrivilegeDeprecationsService, + packageInfo: PackageInfo +): Promise { + const response: PrivilegeDeprecationsRolesResponse = + await privilegeDeprecationService.getKibanaRoles({ context }); + if (response.errors) { + return response.errors; + } + + const rolesWithBasePrivileges = (response.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', + documentationUrl: `https://www.elastic.co/guide/en/kibana/${packageInfo.branch}/kibana-privileges.html`, + correctiveActions: { + manualSteps: [ + 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: + "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(', '), + }, + }), + ], + }, + }, + ]; +} 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 e889eb17d5af9b..f795f3e0a46d52 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,70 @@ 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('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( @@ -56,7 +109,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 +183,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 +262,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 +283,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 +315,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 df212d5c7bde39..7a35a0099e7ac6 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 7cae0d29bf9430..5869ae67c1faa1 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 4784e14a11fb48..596fddd0c416fb 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/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index bf540d5d4ddc82..ffe8793448a746 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!, + }); } } 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 6def1b7d77df3d..ab6d8f6fdf8a99 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, }); 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 213beb62071841..aeee6f15a950f6 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 b56583d26261f6..af2d46851affc6 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 c3a359a0c6cf8e..53d145c7ab6357 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -327,8 +327,7 @@ export class Plugin implements ISecuritySolutionPlugin { registerPrivilegeDeprecations({ deprecationsService: core.deprecations, - getKibanaRolesByFeatureId: - plugins.security?.privilegeDeprecationsService.getKibanaRolesByFeatureId, + getKibanaRoles: plugins.security?.privilegeDeprecationsService.getKibanaRoles, logger: this.logger.get('deprecations'), });