Skip to content

Commit

Permalink
Resolves UI Breaks from Malformed Roles (elastic#131915)
Browse files Browse the repository at this point in the history
* Adds catch of exceptions from PrivilegeSerializer deserialize methods to role transform function.
Resolves 124808 where malformed Elasticsearch roles cause Kibana users and roles UIs to not display correctly.

* Adds logger to role transform functions

* File accidentally not saved prior to last commit
  • Loading branch information
jeramysoucy authored May 12, 2022
1 parent a10d698 commit f4eb311
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { omit, pick } from 'lodash';

import { KibanaFeature } from '@kbn/features-plugin/server';
import { loggerMock } from '@kbn/logging-mocks';

import { transformElasticsearchRoleToRole } from './elasticsearch_role';
import type { ElasticsearchRole } from './elasticsearch_role';
Expand Down Expand Up @@ -80,6 +81,23 @@ const roles = [
enabled: true,
},
},
{
name: 'global-malformed',
cluster: [],
indices: [],
applications: [
{
application: 'kibana-.kibana',
privileges: ['feature_securitySolutionCases.a;;'],
resources: ['*'],
},
],
run_as: [],
metadata: {},
transient_metadata: {
enabled: true,
},
},
{
name: 'default-base-all',
cluster: [],
Expand Down Expand Up @@ -148,6 +166,23 @@ const roles = [
enabled: true,
},
},
{
name: 'default-malformed',
cluster: [],
indices: [],
applications: [
{
application: 'kibana-.kibana',
privileges: ['feature_securitySolutionCases.a;;'],
resources: ['space:default'],
},
],
run_as: [],
metadata: {},
transient_metadata: {
enabled: true,
},
},
];

function testRoles(
Expand All @@ -161,7 +196,8 @@ function testRoles(
features,
omit(role, 'name'),
role.name,
'kibana-.kibana'
'kibana-.kibana',
loggerMock.create()
);
return pick(transformedRole, ['name', '_transform_error']);
});
Expand Down Expand Up @@ -228,10 +264,12 @@ describe('#transformElasticsearchRoleToRole', () => {
{ name: 'global-base-read', _transform_error: [] },
{ name: 'global-foo-all', _transform_error: [] },
{ name: 'global-foo-read', _transform_error: [] },
{ name: 'global-malformed', _transform_error: ['kibana'] },
{ name: 'default-base-all', _transform_error: [] },
{ name: 'default-base-read', _transform_error: [] },
{ name: 'default-foo-all', _transform_error: ['kibana'] },
{ name: 'default-foo-read', _transform_error: [] },
{ name: 'default-malformed', _transform_error: ['kibana'] },
]);

testRoles(
Expand All @@ -243,10 +281,12 @@ describe('#transformElasticsearchRoleToRole', () => {
{ name: 'global-base-read', _transform_error: [] },
{ name: 'global-foo-all', _transform_error: [] },
{ name: 'global-foo-read', _transform_error: ['kibana'] },
{ name: 'global-malformed', _transform_error: ['kibana'] },
{ name: 'default-base-all', _transform_error: [] },
{ name: 'default-base-read', _transform_error: [] },
{ name: 'default-foo-all', _transform_error: [] },
{ name: 'default-foo-read', _transform_error: ['kibana'] },
{ name: 'default-malformed', _transform_error: ['kibana'] },
]
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
* 2.0.
*/

import type { Logger } from '@kbn/core/server';
import type { KibanaFeature } from '@kbn/features-plugin/common';

import {
GLOBAL_RESOURCE,
RESERVED_PRIVILEGES_APPLICATION_WILDCARD,
} from '../../../common/constants';
import type { Role, RoleKibanaPrivilege } from '../../../common/model';
import { getDetailedErrorMessage } from '../../errors';
import { PrivilegeSerializer } from '../privilege_serializer';
import { ResourceSerializer } from '../resource_serializer';

Expand All @@ -30,12 +32,14 @@ export function transformElasticsearchRoleToRole(
features: KibanaFeature[],
elasticsearchRole: Omit<ElasticsearchRole, 'name'>,
name: string,
application: string
application: string,
logger: Logger
): Role {
const kibanaTransformResult = transformRoleApplicationsToKibanaPrivileges(
features,
elasticsearchRole.applications,
application
application,
logger
);
return {
name,
Expand All @@ -58,7 +62,8 @@ export function transformElasticsearchRoleToRole(
function transformRoleApplicationsToKibanaPrivileges(
features: KibanaFeature[],
roleApplications: ElasticsearchRole['applications'],
application: string
application: string,
logger: Logger
) {
const roleKibanaApplications = roleApplications.filter(
(roleApplication) =>
Expand Down Expand Up @@ -226,9 +231,9 @@ function transformRoleApplicationsToKibanaPrivileges(
};
}

return {
success: true,
value: roleKibanaApplications.map(({ resources, privileges }) => {
// try/catch block ensures graceful return on deserialize exceptions
try {
const transformResult = roleKibanaApplications.map(({ resources, privileges }) => {
// if we're dealing with a global entry, which we've ensured above is only possible if it's the only item in the array
if (resources.length === 1 && resources[0] === GLOBAL_RESOURCE) {
const reservedPrivileges = privileges.filter((privilege) =>
Expand Down Expand Up @@ -288,8 +293,18 @@ function transformRoleApplicationsToKibanaPrivileges(
}, {} as RoleKibanaPrivilege['feature']),
spaces: resources.map((resource) => ResourceSerializer.deserializeSpaceResource(resource)),
};
}),
};
});

return {
success: true,
value: transformResult,
};
} catch (e) {
logger.error(`Error transforming Elasticsearch role: ${getDetailedErrorMessage(e)}`);
return {
success: false,
};
}
}

const extractUnrecognizedApplicationNames = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export const getPrivilegeDeprecationsService = ({
// @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]`
elasticsearchRole,
roleName,
authz.applicationName
authz.applicationName,
logger
)
);
} catch (e) {
Expand Down
10 changes: 8 additions & 2 deletions x-pack/plugins/security/server/routes/authorization/roles/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { wrapIntoCustomErrorResponse } from '../../../errors';
import { createLicensedRouteHandler } from '../../licensed_route_handler';
import { transformElasticsearchRoleToRole } from './model';

export function defineGetRolesRoutes({ router, authz, getFeatures }: RouteDefinitionParams) {
export function defineGetRolesRoutes({
router,
authz,
getFeatures,
logger,
}: RouteDefinitionParams) {
router.get(
{
path: '/api/security/role/{name}',
Expand All @@ -38,7 +43,8 @@ export function defineGetRolesRoutes({ router, authz, getFeatures }: RouteDefini
// @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]`
elasticsearchRole,
request.params.name,
authz.applicationName
authz.applicationName,
logger
),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import { wrapIntoCustomErrorResponse } from '../../../errors';
import { createLicensedRouteHandler } from '../../licensed_route_handler';
import { transformElasticsearchRoleToRole } from './model';

export function defineGetAllRolesRoutes({ router, authz, getFeatures }: RouteDefinitionParams) {
export function defineGetAllRolesRoutes({
router,
authz,
getFeatures,
logger,
}: RouteDefinitionParams) {
router.get(
{ path: '/api/security/role', validate: false },
createLicensedRouteHandler(async (context, request, response) => {
Expand All @@ -30,7 +35,8 @@ export function defineGetAllRolesRoutes({ router, authz, getFeatures }: RouteDef
// @ts-expect-error @elastic/elasticsearch SecurityIndicesPrivileges.names expected to be string[]
elasticsearchRole,
roleName,
authz.applicationName
authz.applicationName,
logger
)
)
.sort((roleA, roleB) => {
Expand Down

0 comments on commit f4eb311

Please sign in to comment.