Skip to content

Commit

Permalink
[APM][9.0] Adding kibana upgrade deprecation warning apm_user removed (
Browse files Browse the repository at this point in the history
…elastic#200163)

Related to: elastic/elasticsearch#116712

Meta issue: elastic#116760
The apm_user role was
elastic/elasticsearch#68749 in 7.13 and was
supposed to be removed in 8.0.
All mentions of apm_user role were finally removed in
elastic#132790.

This PR adds some deprecation steps for users are using the `apm_user`.

<img width="494" alt="Screenshot 2024-11-18 at 14 10 08"
src="https://github.com/user-attachments/assets/e04fff63-b56e-4a74-93ad-a87884f9a8a6">
<img width="1128" alt="Screenshot 2024-11-18 at 14 10 18"
src="https://github.com/user-attachments/assets/de67afcf-8bd5-4896-815a-4e1adc730681">

<img width="1241" alt="Screenshot 2024-11-14 at 13 12 01"
src="https://github.com/user-attachments/assets/e94b0367-b459-45b4-923e-1de7a095b6b8">
  • Loading branch information
cauemarcondes authored and paulinashakirova committed Nov 26, 2024
1 parent 862f5f3 commit 8c1485b
Show file tree
Hide file tree
Showing 11 changed files with 463 additions and 218 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 type { GetDeprecationsContext, IScopedClusterClient, CoreSetup } from '@kbn/core/server';
import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { getDeprecationsInfo } from './apm_user_role';
import { SecurityPluginSetup } from '@kbn/security-plugin/server';

let context: GetDeprecationsContext;
let esClient: jest.Mocked<IScopedClusterClient>;
const core = { docLinks: { version: 'main' } } as unknown as CoreSetup;
const logger = loggingSystemMock.createLogger();
const security = { license: { isEnabled: () => true } } as unknown as SecurityPluginSetup;

describe('apm_user deprecation', () => {
beforeEach(async () => {
esClient = elasticsearchServiceMock.createScopedClusterClient();
esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({
xyz: { username: 'normal_user', roles: ['data_analyst'] },
});
esClient.asCurrentUser.security.getRoleMapping = jest.fn().mockResolvedValue({});

context = { esClient } as unknown as GetDeprecationsContext;
});

test('logs no deprecations when setup has no issues', async () => {
expect(await getDeprecationsInfo(context, core, { logger, security })).toMatchInlineSnapshot(
`Array []`
);
});

describe('users assigned to a removed role', () => {
test('logs a deprecation when a user was found with a removed apm_user role', async () => {
esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({
foo: {
username: 'foo',
roles: ['kibana_admin', 'apm_user'],
},
});

expect(await getDeprecationsInfo(context, core, { logger, security })).toMatchSnapshot();
});
});

describe('roles mapped to a removed role', () => {
test('logs a deprecation when a role was found that maps to the removed apm_user role', async () => {
esClient.asCurrentUser.security.getRoleMapping = jest
.fn()
.mockResolvedValue({ dungeon_master: { roles: ['apm_user'] } });

expect(await getDeprecationsInfo(context, core, { logger, security })).toMatchSnapshot();
});
});

describe('check deprecations when security is disabled', () => {
test('logs no deprecations', async () => {
expect(
await getDeprecationsInfo(context, core, { logger, security: undefined })
).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, core, { logger, security })).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/main/xpack-security.html#_required_permissions_7",
"level": "fetch_error",
"message": "You do not have enough permissions to fix this deprecation.",
"title": "Check for users assigned the deprecated \\"apm_user\\" role",
},
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/main/xpack-security.html#_required_permissions_7",
"level": "fetch_error",
"message": "You do not have enough permissions to fix this deprecation.",
"title": "Check for role mappings using the deprecated \\"apm_user\\" role",
},
]
`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* 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 {
SecurityGetRoleMappingResponse,
SecurityGetUserResponse,
} from '@elastic/elasticsearch/lib/api/types';
import type {
CoreSetup,
DeprecationsDetails,
DocLinksServiceSetup,
ElasticsearchClient,
GetDeprecationsContext,
} from '@kbn/core/server';
import { i18n } from '@kbn/i18n';
import type { DeprecationApmDeps } from '.';
import { deprecations } from '../lib/deprecations';

const APM_USER_ROLE_NAME = 'apm_user';
const getKibanaPrivilegesDocumentationUrl = (branch: string) => {
return `https://www.elastic.co/guide/en/kibana/${branch}/kibana-privileges.html`;
};

export async function getDeprecationsInfo(
{ esClient }: GetDeprecationsContext,
core: CoreSetup,
apmDeps: DeprecationApmDeps
) {
const client = esClient.asCurrentUser;
const { docLinks } = core;
const { security } = apmDeps;

// Nothing to do if security is disabled
if (!security?.license.isEnabled()) {
return [];
}

const [userDeprecations, roleMappingDeprecations] = await Promise.all([
getUsersDeprecations(client, apmDeps, docLinks),
getRoleMappingsDeprecations(client, apmDeps, docLinks),
]);

return [...userDeprecations, ...roleMappingDeprecations];
}

async function getUsersDeprecations(
client: ElasticsearchClient,
apmDeps: DeprecationApmDeps,
docLinks: DocLinksServiceSetup
): Promise<DeprecationsDetails[]> {
const title = i18n.translate('xpack.apm.deprecations.apmUser.title', {
defaultMessage: `Check for users assigned the deprecated "{apmUserRoleName}" role`,
values: { apmUserRoleName: APM_USER_ROLE_NAME },
});

let users: SecurityGetUserResponse;
try {
users = await client.security.getUser();
} catch (err) {
const { logger } = apmDeps;
if (deprecations.getErrorStatusCode(err) === 403) {
logger.warn(
'Failed to retrieve users when checking for deprecations: the "read_security" or "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(title, err, docLinks);
}

const apmUsers = Object.values(users).flatMap((user) =>
user.roles.find(hasApmUserRole) ? user.username : []
);

if (apmUsers.length === 0) {
return [];
}

return [
{
title,
message: i18n.translate('xpack.apm.deprecations.apmUser.description', {
defaultMessage: `The "{apmUserRoleName}" role has been deprecated. Remove the "{apmUserRoleName}" role from affected users in this cluster including: {users}`,
values: { apmUserRoleName: APM_USER_ROLE_NAME, users: apmUsers.join() },
}),
correctiveActions: {
manualSteps: [
i18n.translate('xpack.apm.deprecations.apmUser.manualStepOne', {
defaultMessage: `Go to Management > Security > Users to find users with the "{apmUserRoleName}" role.`,
values: { apmUserRoleName: APM_USER_ROLE_NAME },
}),
i18n.translate('xpack.apm.deprecations.apmUser.manualStepTwo', {
defaultMessage:
'Remove the "{apmUserRoleName}" role from all users and add the built-in "viewer" role.',
values: { apmUserRoleName: APM_USER_ROLE_NAME },
}),
],
},
level: 'critical',
deprecationType: 'feature',
documentationUrl: getKibanaPrivilegesDocumentationUrl(docLinks.version),
},
];
}

async function getRoleMappingsDeprecations(
client: ElasticsearchClient,
apmDeps: DeprecationApmDeps,
docLinks: DocLinksServiceSetup
): Promise<DeprecationsDetails[]> {
const title = i18n.translate('xpack.apm.deprecations.apmUserRoleMappings.title', {
defaultMessage: `Check for role mappings using the deprecated "{apmUserRoleName}" role`,
values: { apmUserRoleName: APM_USER_ROLE_NAME },
});

let roleMappings: SecurityGetRoleMappingResponse;
try {
roleMappings = await client.security.getRoleMapping();
} catch (err) {
const { logger } = apmDeps;
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(title, err, docLinks);
}

const roleMappingsWithApmUserRole = Object.entries(roleMappings).flatMap(([roleName, role]) =>
role.roles?.find(hasApmUserRole) ? roleName : []
);

if (roleMappingsWithApmUserRole.length === 0) {
return [];
}

return [
{
title,
message: i18n.translate('xpack.apm.deprecations.apmUserRoleMappings.description', {
defaultMessage: `The "{apmUserRoleName}" role has been deprecated. Remove the "{apmUserRoleName}" role from affected role mappings in this cluster including: {roles}`,
values: {
apmUserRoleName: APM_USER_ROLE_NAME,
roles: roleMappingsWithApmUserRole.join(),
},
}),
correctiveActions: {
manualSteps: [
i18n.translate('xpack.apm.deprecations.apmUserRoleMappings.manualStepOne', {
defaultMessage: `Go to Management > Security > Role Mappings to find roles mappings with the "{apmUserRoleName}" role.`,
values: { apmUserRoleName: APM_USER_ROLE_NAME },
}),
i18n.translate('xpack.apm.deprecations.apmUserRoleMappings.manualStepTwo', {
defaultMessage:
'Remove the "{apmUserRoleName}" role from all role mappings and add the built-in "viewer" role',
values: { apmUserRoleName: APM_USER_ROLE_NAME },
}),
],
},
level: 'critical',
deprecationType: 'feature',
documentationUrl: getKibanaPrivilegesDocumentationUrl(docLinks.version),
},
];
}

const hasApmUserRole = (role: string) => role === APM_USER_ROLE_NAME;
Loading

0 comments on commit 8c1485b

Please sign in to comment.