From 4847e72091de7b17a8c0b4328dc115c9ab4087f9 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 14 Oct 2021 18:48:38 -0700 Subject: [PATCH] [Reporting] Add deprecation messages for roles mapped to reporting_user This fixes the deprecations for various edge cases: - when security was disabled, users saw a meaningless error message - when users did not have manage_security privilege they saw a meaningless error message Adds support for checking deprecations related to xpack.reporting.roles.allow --- .../reporting/server/config/index.test.ts | 2 +- .../plugins/reporting/server/config/index.ts | 2 +- x-pack/plugins/reporting/server/core.ts | 1 + .../__snapshots__/reporting_role.test.ts.snap | 83 +++++++ .../deprecations/reporting_role.test.ts | 165 +++++++------ .../server/deprecations/reporting_role.ts | 232 ++++++++++++++---- .../server/lib/deprecations/index.ts | 74 ++++++ .../server/routes/diagnostic/browser.test.ts | 12 +- .../routes/diagnostic/screenshot.test.ts | 12 +- .../create_mock_reportingplugin.ts | 8 +- 10 files changed, 460 insertions(+), 131 deletions(-) create mode 100644 x-pack/plugins/reporting/server/deprecations/__snapshots__/reporting_role.test.ts.snap diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts index 1a75f6dfec3bd..f77713551592b 100644 --- a/x-pack/plugins/reporting/server/config/index.test.ts +++ b/x-pack/plugins/reporting/server/config/index.test.ts @@ -49,7 +49,7 @@ describe('deprecations', () => { const { messages } = applyReportingDeprecations({ roles: { enabled: true } }); expect(messages).toMatchInlineSnapshot(` Array [ - "Use Kibana application privileges to grant reporting privileges. Using \\"xpack.reporting.roles.allow\\" to grant reporting privileges prevents users from using API Keys to create reports. The \\"xpack.reporting.roles.enabled\\" setting will default to false in a future release.", + "Use Kibana application privileges to grant reporting privileges. Using \\"xpack.reporting.roles.allow\\" to grant reporting privileges is deprecated. The \\"xpack.reporting.roles.enabled\\" setting will default to false in a future release.", ] `); }); diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index 1eeafb4e0c513..ecde65a27623c 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -64,7 +64,7 @@ export const config: PluginConfigDescriptor = { defaultMessage: `Use Kibana application privileges to grant reporting privileges.` + ` Using "{fromPath}.roles.allow" to grant reporting privileges` + - ` prevents users from using API Keys to create reports.` + + ` is deprecated.` + ` The "{fromPath}.roles.enabled" setting will default to false` + ` in a future release.`, values: { fromPath }, diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index e09cee8c3c7c2..a4986acc0253b 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -72,6 +72,7 @@ export class ReportingCore { public getContract: () => ReportingSetup; constructor(private logger: LevelLogger, context: PluginInitializerContext) { + // TODO: capture the entire packageInfo so we can form documentation links on the server this.kibanaVersion = context.env.packageInfo.version; const syncConfig = context.config.get(); this.deprecatedAllowedRoles = syncConfig.roles.enabled ? syncConfig.roles.allow : false; diff --git a/x-pack/plugins/reporting/server/deprecations/__snapshots__/reporting_role.test.ts.snap b/x-pack/plugins/reporting/server/deprecations/__snapshots__/reporting_role.test.ts.snap new file mode 100644 index 0000000000000..df430b301eb44 --- /dev/null +++ b/x-pack/plugins/reporting/server/deprecations/__snapshots__/reporting_role.test.ts.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`roles mapped to a deprecated role logs a deprecation when a role was found that maps to a deprecated custom role from the roles.allow setting 1`] = ` +Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", + "Remove \\"xpack.reporting.roles.allow\\" in kibana.yml", + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired. You may remove the \\"reporting_user\\" role from the user(s) .", + "Remove the role that grants Reporting privilege from all role mappings and add the custom role. The affected role mappings are: dungeon_master[my_test_reporting_user].", + ], + }, + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html#grant-user-access", + "level": "warning", + "message": "Existing roles are mapped to a deprecated role for Reporting privileges", + "title": "The \\"reporting_user\\" role is deprecated: check role mappings", + }, +] +`; + +exports[`roles mapped to a deprecated role logs a deprecation when a role was found that maps to the deprecated reporting_user role 1`] = ` +Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", + "Remove \\"xpack.reporting.roles.allow\\" in kibana.yml", + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired. You may remove the \\"reporting_user\\" role from the user(s) .", + "Remove the role that grants Reporting privilege from all role mappings and add the custom role. The affected role mappings are: dungeon_master[reporting_user].", + ], + }, + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html#grant-user-access", + "level": "warning", + "message": "Existing roles are mapped to a deprecated role for Reporting privileges", + "title": "The \\"reporting_user\\" role is deprecated: check role mappings", + }, +] +`; + +exports[`users assigned to a deprecated role logs a deprecation when a user was found with a deprecated custom role from the roles.allow setting 1`] = ` +Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", + "Remove \\"xpack.reporting.roles.allow\\" in kibana.yml", + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired. You may remove the \\"reporting_user\\" role from the user(s). The affected users are: reportron[my_test_reporting_user].", + ], + }, + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html#grant-user-access", + "level": "warning", + "message": "Existing users have their Reporting privilege granted by a deprecated setting.", + "title": "The \\"reporting_user\\" role is deprecated: check user roles", + }, +] +`; + +exports[`users assigned to a deprecated role logs a deprecation when a user was found with a deprecated reporting_user role 1`] = ` +Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", + "Remove \\"xpack.reporting.roles.allow\\" in kibana.yml", + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired. You may remove the \\"reporting_user\\" role from the user(s). The affected users are: reportron[reporting_user].", + ], + }, + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html#grant-user-access", + "level": "warning", + "message": "Existing users have their Reporting privilege granted by a deprecated setting.", + "title": "The \\"reporting_user\\" role is deprecated: check user roles", + }, +] +`; diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts index 5b0719bf6e6b6..da08ea8e091c4 100644 --- a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts @@ -5,11 +5,15 @@ * 2.0. */ +import { GetDeprecationsContext, IScopedClusterClient } from 'kibana/server'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { ReportingCore } from '..'; +import { + createMockConfigSchema, + createMockPluginSetup, + createMockReportingCore, +} from '../test_helpers'; import { getDeprecationsInfo } from './reporting_role'; -import { createMockConfigSchema, createMockReportingCore } from '../test_helpers'; -import { elasticsearchServiceMock } from 'src/core/server/mocks'; -import { GetDeprecationsContext, IScopedClusterClient } from 'kibana/server'; let reportingCore: ReportingCore; let context: GetDeprecationsContext; @@ -18,6 +22,7 @@ let esClient: jest.Mocked; beforeEach(async () => { const mockReportingConfig = createMockConfigSchema({ roles: { enabled: false } }); reportingCore = await createMockReportingCore(mockReportingConfig); + esClient = elasticsearchServiceMock.createScopedClusterClient(); esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ body: { xyz: { username: 'normal_user', roles: ['data_analyst'] } }, @@ -26,95 +31,111 @@ beforeEach(async () => { }); test('logs no deprecations when setup has no issues', async () => { - expect( - await getDeprecationsInfo(context, { - reportingCore, - }) - ).toMatchInlineSnapshot(`Array []`); + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(`Array []`); }); -test('logs a plain message when only a reporting_user role issue is found', async () => { - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, +describe('users assigned to a deprecated role', () => { + test('logs a deprecation when a user was found with a deprecated reporting_user role', async () => { + esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ + body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, + }); + + const mockReportingConfig = createMockConfigSchema({ roles: { enabled: true } }); + reportingCore = await createMockReportingCore(mockReportingConfig); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchSnapshot(); }); - expect( - await getDeprecationsInfo(context, { - reportingCore, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveActions": Object { - "manualSteps": Array [ - "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", - "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", - ], - }, - "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", - "level": "critical", - "message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"", - "title": "Found deprecated reporting role", + + test('logs a deprecation when a user was found with a deprecated custom role from the roles.allow setting', async () => { + reportingCore = await createMockReportingCore( + createMockConfigSchema({ roles: { enabled: true, allow: ['my_test_reporting_user'] } }) + ); + esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ + body: { + reportron: { username: 'reportron', roles: ['kibana_admin', 'my_test_reporting_user'] }, }, - ] - `); + }); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchSnapshot(); + }); }); -test('logs multiple entries when multiple reporting_user role issues are found', async () => { - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { - reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] }, - supercooluser: { username: 'supercooluser', roles: ['kibana_admin', 'reporting_user'] }, - }, +describe('roles mapped to a deprecated role', () => { + test('logs a deprecation when a role was found that maps to the deprecated reporting_user role', async () => { + esClient.asCurrentUser.security.getRoleMapping = jest.fn().mockResolvedValue({ + body: { dungeon_master: { roles: ['reporting_user'] } }, + }); + + const mockReportingConfig = createMockConfigSchema({ roles: { enabled: true } }); + reportingCore = await createMockReportingCore(mockReportingConfig); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchSnapshot(); }); - expect( - await getDeprecationsInfo(context, { - reportingCore, - }) - ).toMatchInlineSnapshot(` + test('logs a deprecation when a role was found that maps to a deprecated custom role from the roles.allow setting', async () => { + reportingCore = await createMockReportingCore( + createMockConfigSchema({ roles: { enabled: true, allow: ['my_test_reporting_user'] } }) + ); + esClient.asCurrentUser.security.getRoleMapping = jest.fn().mockResolvedValue({ + body: { dungeon_master: { roles: ['my_test_reporting_user'] } }, + }); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchSnapshot(); + }); +}); + +describe('check deprecations when security is disabled', () => { + test('logs no deprecations: roles enabled', async () => { + const mockReportingConfig = createMockConfigSchema({ roles: { enabled: false } }); + reportingCore = await createMockReportingCore( + mockReportingConfig, + createMockPluginSetup({ security: null }) + ); + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(`Array []`); + }); + + test('logs no deprecations: roles not enabled', async () => { + const mockReportingConfig = createMockConfigSchema(); // roles.enabled: true is default in 7.x / 8.0 + reportingCore = await createMockReportingCore( + mockReportingConfig, + createMockPluginSetup({ security: null }) + ); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(`Array []`); + }); +}); + +it('insufficient permissions', async () => { + const permissionsError = new Error('you shall not pass'); + (permissionsError as unknown as { statusCode: number }).statusCode = 403; + esClient.asCurrentUser.security.getUser = jest.fn().mockRejectedValue(permissionsError); + esClient.asCurrentUser.security.getRoleMapping = jest.fn().mockRejectedValue(permissionsError); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(` Array [ Object { "correctiveActions": Object { "manualSteps": Array [ - "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", - "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", + "Make sure you have a \\"manage_security\\" cluster privilege assigned.", ], }, - "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", - "level": "critical", - "message": "The deprecated \\"reporting_user\\" role has been found for 2 user(s): \\"reportron\\", \\"supercooluser\\"", - "title": "Found deprecated reporting role", + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/xpack-security.html#_required_permissions_7", + "level": "fetch_error", + "message": "You do not have enough permissions to fix this deprecation.", + "title": "The \\"reporting_user\\" role is deprecated: check user roles", }, - ] - `); -}); - -test('logs an expanded message when a config issue and a reporting_user role issue is found', async () => { - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, - }); - - const mockReportingConfig = createMockConfigSchema({ roles: { enabled: true } }); - reportingCore = await createMockReportingCore(mockReportingConfig); - - expect( - await getDeprecationsInfo(context, { - reportingCore, - }) - ).toMatchInlineSnapshot(` - Array [ Object { "correctiveActions": Object { "manualSteps": Array [ - "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", - "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", - "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", + "Make sure you have a \\"manage_security\\" cluster privilege assigned.", ], }, - "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", - "level": "critical", - "message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"", - "title": "Found deprecated reporting role", + "deprecationType": "feature", + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/xpack-security.html#_required_permissions_7", + "level": "fetch_error", + "message": "You do not have enough permissions to fix this deprecation.", + "title": "The \\"reporting_user\\" role is deprecated: check role mappings", }, ] `); diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts index 6e08169727d1b..fad35cb933fa9 100644 --- a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts @@ -5,65 +5,209 @@ * 2.0. */ -import type { GetDeprecationsContext, DeprecationsDetails } from 'src/core/server'; +import { + SecurityGetRoleMappingResponse, + SecurityGetUserResponse, +} from '@elastic/elasticsearch/api/types'; import { i18n } from '@kbn/i18n'; -import { ReportingCore } from '..'; +import type { + DeprecationsDetails, + ElasticsearchClient, + GetDeprecationsContext, +} from 'src/core/server'; +import { ReportingCore } from '../'; +import { deprecations } from '../lib/deprecations'; + +const REPORTING_USER_ROLE_NAME = 'reporting_user'; +const DOCUMENTATION_URL = + 'https://www.elastic.co/guide/en/kibana/current/secure-reporting.html#grant-user-access'; -const deprecatedRole = 'reporting_user'; const upgradableConfig = 'xpack.reporting.roles.enabled: false'; +const incompatibleConfig = 'xpack.reporting.roles.allow'; interface ExtraDependencies { reportingCore: ReportingCore; } -export const getDeprecationsInfo = async ( +export async function getDeprecationsInfo( { esClient }: GetDeprecationsContext, { reportingCore }: ExtraDependencies -): Promise => { - const usingDeprecatedConfig = !reportingCore.getContract().usesUiCapabilities(); - const deprecations: DeprecationsDetails[] = []; - const { body: users } = await esClient.asCurrentUser.security.getUser(); +): Promise { + const client = esClient.asCurrentUser; + const { security } = reportingCore.getPluginSetupDeps(); + + // Nothing to do if security is disabled + if (!security) { + return []; + } - const reportingUsers = Object.entries(users) - .filter(([, user]) => user.roles.includes(deprecatedRole)) - .map(([, user]) => user.username); + const config = reportingCore.getConfig(); + const deprecatedRoles = config.get('roles', 'allow') || ['reporting_user']; - const numReportingUsers = reportingUsers.length; + return [ + ...(await getUsersDeprecations(client, reportingCore, deprecatedRoles)), + ...(await getRoleMappingsDeprecations(client, reportingCore, deprecatedRoles)), + ]; +} - if (numReportingUsers > 0) { - deprecations.push({ - title: i18n.translate('xpack.reporting.deprecations.reportingRoleTitle', { - defaultMessage: 'Found deprecated reporting role', +async function getUsersDeprecations( + client: ElasticsearchClient, + reportingCore: ReportingCore, + deprecatedRoles: string[] +): Promise { + const strings = { + title: i18n.translate('xpack.reporting.deprecations.reportingRoleUsersTitle', { + defaultMessage: `The "{reportingUserRoleName}" role is deprecated: check user roles`, + values: { reportingUserRoleName: REPORTING_USER_ROLE_NAME }, + }), + message: i18n.translate('xpack.reporting.deprecations.reportingRoleUsersMessage', { + defaultMessage: `Existing users have their Reporting privilege granted by a deprecated setting.`, + }), + manualSteps: (usersRoles: string) => [ + i18n.translate('xpack.reporting.deprecations.reportingRoleUsers.manualStepOne', { + defaultMessage: 'Set "{upgradableConfig}" in kibana.yml', + values: { upgradableConfig }, }), - message: i18n.translate('xpack.reporting.deprecations.reportingRoleMessage', { + i18n.translate('xpack.reporting.deprecations.reportingRoleUsers.manualStepTwo', { + defaultMessage: 'Remove "{incompatibleConfig}" in kibana.yml', + values: { incompatibleConfig }, + }), + i18n.translate('xpack.reporting.deprecations.reportingRoleUsers.manualStepThree', { defaultMessage: - 'The deprecated "{deprecatedRole}" role has been found for {numReportingUsers} user(s): "{usernames}"', - values: { deprecatedRole, numReportingUsers, usernames: reportingUsers.join('", "') }, + `Create one or more custom roles that provide Kibana application` + + ` privileges to reporting features in **Management > Security > Roles**.`, }), - documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/secure-reporting.html', - level: 'critical', - correctiveActions: { - manualSteps: [ - ...(usingDeprecatedConfig - ? [ - i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepOneMessage', { - defaultMessage: 'Set "{upgradableConfig}" in kibana.yml', - values: { upgradableConfig }, - }), - ] - : []), - i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepTwoMessage', { - defaultMessage: `Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.`, - }), - i18n.translate('xpack.reporting.deprecations.reportingRole.manualStepThreeMessage', { - defaultMessage: - 'Assign the custom role(s) as desired, and remove the "{deprecatedRole}" role from the user(s).', - values: { deprecatedRole }, - }), - ], - }, - }); + i18n.translate('xpack.reporting.deprecations.reportingRoleUsers.manualStepFour', { + defaultMessage: + 'Assign the custom role(s) as desired. You may remove the "{deprecatedRole}" role from the user(s). The affected users are: {usersRoles}.', + values: { deprecatedRole: `reporting_user`, usersRoles }, + }), + ], + }; + + let users: SecurityGetUserResponse; + try { + users = (await client.security.getUser()).body; + } catch (err) { + const { logger } = reportingCore.getPluginSetupDeps(); + if (deprecations.getErrorStatusCode(err) === 403) { + logger.warn( + `Failed to retrieve users when checking for deprecations:` + + ` the "manage_security" cluster privilege is required.` + ); + } else { + logger.error( + `Failed to retrieve users when checking for deprecations,` + + ` unexpected error: ${deprecations.getDetailedErrorMessage(err)}.` + ); + } + return deprecations.deprecationError(strings.title, err); + } + + const reportingUsers = Object.entries(users).reduce((userSet, current) => { + const [userName, user] = current; + const foundRole = user.roles.find((role) => deprecatedRoles.includes(role)); + return foundRole ? [...userSet, `${userName}[${foundRole}]`] : userSet; + }, [] as string[]); + + if (reportingUsers.length === 0) { + return []; + } + + return [ + { + title: strings.title, + message: strings.message, + correctiveActions: { manualSteps: strings.manualSteps(reportingUsers.join(', ')) }, + level: 'warning', + deprecationType: 'feature', + documentationUrl: DOCUMENTATION_URL, + }, + ]; +} + +async function getRoleMappingsDeprecations( + client: ElasticsearchClient, + reportingCore: ReportingCore, + deprecatedRoles: string[] +): Promise { + const strings = { + title: i18n.translate('xpack.reporting.deprecations.reportingRoleMappingsTitle', { + defaultMessage: `The "{reportingUserRoleName}" role is deprecated: check role mappings`, + values: { reportingUserRoleName: REPORTING_USER_ROLE_NAME }, + }), + message: i18n.translate('xpack.reporting.deprecations.reportingRoleMappingsMessage', { + defaultMessage: `Existing roles are mapped to a deprecated role for Reporting privileges`, + }), + manualSteps: (roleMappings: string) => [ + i18n.translate('xpack.reporting.deprecations.reportingRoleMappings.manualStepOne', { + defaultMessage: 'Set "{upgradableConfig}" in kibana.yml', + values: { upgradableConfig }, + }), + i18n.translate('xpack.reporting.deprecations.reportingRoleMappings.manualStepTwo', { + defaultMessage: 'Remove "{incompatibleConfig}" in kibana.yml', + values: { incompatibleConfig }, + }), + i18n.translate('xpack.reporting.deprecations.reportingRoleMappings.manualStepThree', { + defaultMessage: + `Create one or more custom roles that provide Kibana application` + + ` privileges to reporting features in **Management > Security > Roles**.`, + }), + i18n.translate('xpack.reporting.deprecations.reportingRoleMappings.manualStepFour', { + defaultMessage: + 'Assign the custom role(s) as desired. You may remove the "{deprecatedRole}" role from the user(s) .', + values: { deprecatedRole: `reporting_user` }, + }), + i18n.translate('xpack.reporting.deprecations.reportingRoleMappings.manualStepFive', { + defaultMessage: + 'Remove the role that grants Reporting privilege from all role mappings and add the custom role. The affected role mappings are: {roleMappings}.', + values: { roleMappings }, + }), + ], + }; + + let roleMappings: SecurityGetRoleMappingResponse; + try { + roleMappings = (await client.security.getRoleMapping()).body; + } catch (err) { + const { logger } = reportingCore.getPluginSetupDeps(); + if (deprecations.getErrorStatusCode(err) === 403) { + logger.warn( + `Failed to retrieve role mappings when checking for deprecations:` + + ` the "manage_security" cluster privilege is required.` + ); + } else { + logger.error( + `Failed to retrieve role mappings when checking for deprecations,` + + ` unexpected error: ${deprecations.getDetailedErrorMessage(err)}.` + ); + } + return deprecations.deprecationError(strings.title, err); + } + + const roleMappingsWithReportingRole: string[] = Object.entries(roleMappings).reduce( + (roleSet, current) => { + const [roleName, role] = current; + const foundMapping = role.roles.find((roll) => deprecatedRoles.includes(roll)); + return foundMapping ? [...roleSet, `${roleName}[${foundMapping}]`] : roleSet; + }, + [] as string[] + ); + + if (roleMappingsWithReportingRole.length === 0) { + return []; } - return deprecations; -}; + return [ + { + title: strings.title, + message: strings.message, + correctiveActions: { + manualSteps: strings.manualSteps(roleMappingsWithReportingRole.join(', ')), + }, + level: 'warning', + deprecationType: 'feature', + documentationUrl: DOCUMENTATION_URL, + }, + ]; +} diff --git a/x-pack/plugins/reporting/server/lib/deprecations/index.ts b/x-pack/plugins/reporting/server/lib/deprecations/index.ts index 95594940e07e2..2d55c3b4c22d8 100644 --- a/x-pack/plugins/reporting/server/lib/deprecations/index.ts +++ b/x-pack/plugins/reporting/server/lib/deprecations/index.ts @@ -5,8 +5,82 @@ * 2.0. */ +import { errors } from '@elastic/elasticsearch'; +import Boom from '@hapi/boom'; +import { i18n } from '@kbn/i18n'; +import { DeprecationsDetails } from 'kibana/server'; import { checkIlmMigrationStatus } from './check_ilm_migration_status'; +function deprecationError(title: string, error: Error): DeprecationsDetails[] { + if (getErrorStatusCode(error) === 403) { + return [ + { + title, + level: 'fetch_error', // NOTE: is fetch_error not shown in the Upgrade Assistant UI? + deprecationType: 'feature', + message: i18n.translate( + 'xpack.reporting.deprecations.reportingRole.forbiddenErrorMessage', + { defaultMessage: 'You do not have enough permissions to fix this deprecation.' } + ), + documentationUrl: `https://www.elastic.co/guide/en/kibana/current/xpack-security.html#_required_permissions_7`, + correctiveActions: { + manualSteps: [ + i18n.translate( + 'xpack.reporting.deprecations.reportingRole.forbiddenErrorCorrectiveAction', + { + defaultMessage: + 'Make sure you have a "manage_security" cluster privilege assigned.', + } + ), + ], + }, + }, + ]; + } + + return [ + { + title, + level: 'fetch_error', // NOTE: is fetch_error not shown in the Upgrade Assistant UI? + deprecationType: 'feature', + message: i18n.translate('xpack.reporting.deprecations.reportingRole.unknownErrorMessage', { + defaultMessage: 'Failed to perform deprecation check. Check Kibana logs for more details.', + }), + correctiveActions: { + manualSteps: [ + i18n.translate( + 'xpack.reporting.deprecations.reportingRole.unknownErrorCorrectiveAction', + { defaultMessage: 'Check Kibana logs for more details.' } + ), + ], + }, + }, + ]; +} + +function getErrorStatusCode(error: any): number { + if (error instanceof errors.ResponseError) { + return error.statusCode; + } + + return Boom.isBoom(error) ? error.output.statusCode : error.statusCode || error.status; +} + +function getDetailedErrorMessage(error: any): string { + if (error instanceof errors.ResponseError) { + return JSON.stringify(error.body); + } + + if (Boom.isBoom(error)) { + return JSON.stringify(error.output.payload); + } + + return error.message; +} + export const deprecations = { checkIlmMigrationStatus, + deprecationError, + getDetailedErrorMessage, + getErrorStatusCode, }; diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts index f32e1b437bc33..7677f37702f0d 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts @@ -52,11 +52,13 @@ describe('POST /diagnose/browser', () => { () => ({ usesUiCapabilities: () => false }) ); - const mockSetupDeps = createMockPluginSetup({ - router: httpSetup.createRouter(''), - }); - - core = await createMockReportingCore(config, mockSetupDeps); + core = await createMockReportingCore( + config, + createMockPluginSetup({ + router: httpSetup.createRouter(''), + security: null, + }) + ); mockedSpawn.mockImplementation(() => ({ removeAllListeners: jest.fn(), diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts index 6d844f9637a0b..dd543707fe66a 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts @@ -50,11 +50,13 @@ describe('POST /diagnose/screenshot', () => { () => ({ usesUiCapabilities: () => false }) ); - const mockSetupDeps = createMockPluginSetup({ - router: httpSetup.createRouter(''), - }); - - core = await createMockReportingCore(config, mockSetupDeps); + core = await createMockReportingCore( + config, + createMockPluginSetup({ + router: httpSetup.createRouter(''), + security: null, + }) + ); }); afterEach(async () => { diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index 9e58d6d4efa41..d62cc750ccfcc 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -12,11 +12,13 @@ jest.mock('../browsers'); import _ from 'lodash'; import * as Rx from 'rxjs'; import { coreMock, elasticsearchServiceMock } from 'src/core/server/mocks'; -import { FieldFormatsRegistry } from 'src/plugins/field_formats/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { dataPluginMock } from 'src/plugins/data/server/mocks'; +import { FieldFormatsRegistry } from 'src/plugins/field_formats/common'; import { ReportingConfig, ReportingCore } from '../'; import { featuresPluginMock } from '../../../features/server/mocks'; +import { securityMock } from '../../../security/server/mocks'; +import { taskManagerMock } from '../../../task_manager/server/mocks'; import { chromium, HeadlessChromiumDriverFactory, @@ -39,9 +41,9 @@ export const createMockPluginSetup = (setupMock?: any): ReportingInternalSetup = features: featuresPluginMock.createSetup(), basePath: { set: jest.fn() }, router: setupMock.router, - security: setupMock.security, + security: securityMock.createSetup(), licensing: { license$: Rx.of({ isAvailable: true, isActive: true, type: 'basic' }) } as any, - taskManager: { registerTaskDefinitions: jest.fn() } as any, + taskManager: taskManagerMock.createSetup(), logger: createMockLevelLogger(), ...setupMock, };