Skip to content

Commit

Permalink
[Authz] Superuser privileges (#196586)
Browse files Browse the repository at this point in the history
## Summary

This PR adds support for explicit indication whether endpoint is
restricted to superusers only.
Moved `api/encrypted_saved_objects/_rotate_key` endpoint to the new
configuration.

__Relates: https://github.com/elastic/kibana/issues/196271__


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

## Release note

Introduced explicit configuration for routes that require superuser
access. Moved `api/encrypted_saved_objects/_rotate_key` endpoint to the
new configuration.

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
elena-shostak and elasticmachine authored Oct 23, 2024
1 parent a7a81c2 commit 598706c
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { validRouteSecurity } from './security_route_config_validator';
import { ReservedPrivilegesSet } from '@kbn/core-http-server';

describe('RouteSecurity validation', () => {
it('should pass validation for valid route security with authz enabled and valid required privileges', () => {
Expand Down Expand Up @@ -276,4 +277,31 @@ describe('RouteSecurity validation', () => {
`"[authz.requiredPrivileges]: anyRequired privileges must contain unique values"`
);
});

it('should fail validation when anyRequired has superuser privileges set', () => {
const invalidRouteSecurity = {
authz: {
requiredPrivileges: [
{ anyRequired: ['privilege1', 'privilege1'], allRequired: ['privilege4'] },
{ anyRequired: ['privilege5', ReservedPrivilegesSet.superuser] },
],
},
};

expect(() => validRouteSecurity(invalidRouteSecurity)).toThrowErrorMatchingInlineSnapshot(
`"[authz.requiredPrivileges]: Combining superuser with other privileges is redundant, superuser privileges set can be only used as a standalone privilege."`
);
});

it('should fail validation when allRequired combines superuser privileges set with other privileges', () => {
const invalidRouteSecurity = {
authz: {
requiredPrivileges: [ReservedPrivilegesSet.superuser, 'privilege1'],
},
};

expect(() => validRouteSecurity(invalidRouteSecurity)).toThrowErrorMatchingInlineSnapshot(
`"[authz.requiredPrivileges]: Combining superuser with other privileges is redundant, superuser privileges set can be only used as a standalone privilege."`
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import { schema } from '@kbn/config-schema';
import type { RouteSecurity, RouteConfigOptions } from '@kbn/core-http-server';
import { ReservedPrivilegesSet } from '@kbn/core-http-server';
import type { DeepPartial } from '@kbn/utility-types';

const privilegeSetSchema = schema.object(
Expand Down Expand Up @@ -49,6 +50,15 @@ const requiredPrivilegesSchema = schema.arrayOf(
}
});

// Combining superuser with other privileges is redundant.
// If user is a superuser, they inherently have access to all the privileges that may come with other roles.
if (
anyRequired.includes(ReservedPrivilegesSet.superuser) ||
(allRequired.includes(ReservedPrivilegesSet.superuser) && allRequired.length > 1)
) {
return 'Combining superuser with other privileges is redundant, superuser privileges set can be only used as a standalone privilege.';
}

if (anyRequired.length && allRequired.length) {
for (const privilege of anyRequired) {
if (allRequired.includes(privilege)) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/http/core-http-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export {
getResponseValidation,
isFullValidatorContainer,
isKibanaResponse,
ReservedPrivilegesSet,
} from './src/router';

export type { ICspConfig } from './src/csp';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/http/core-http-server/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export type {
PrivilegeSet,
} from './route';

export { validBodyOutput } from './route';
export { validBodyOutput, ReservedPrivilegesSet } from './route';
export type {
RouteValidationFunction,
RouteValidationResultFactory,
Expand Down
8 changes: 8 additions & 0 deletions packages/core/http/core-http-server/src/router/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ export interface RouteSecurity {
authc?: RouteAuthc;
}

/**
* A set of reserved privileges that can be used to check access to the route.
*/
export enum ReservedPrivilegesSet {
operator = 'operator',
superuser = 'superuser',
}

/**
* Additional route options.
* @public
Expand Down
6 changes: 5 additions & 1 deletion src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,11 @@ export type {
} from '@kbn/core-http-server';
export type { IExternalUrlPolicy } from '@kbn/core-http-common';

export { validBodyOutput, OnPostAuthResultType } from '@kbn/core-http-server';
export {
validBodyOutput,
OnPostAuthResultType,
ReservedPrivilegesSet,
} from '@kbn/core-http-server';

export type {
HttpResourcesRenderOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { Type } from '@kbn/config-schema';
import type { IRouter, RequestHandler, RequestHandlerContext, RouteConfig } from '@kbn/core/server';
import { kibanaResponseFactory } from '@kbn/core/server';
import { kibanaResponseFactory, ReservedPrivilegesSet } from '@kbn/core/server';
import { httpServerMock } from '@kbn/core/server/mocks';

import { routeDefinitionParamsMock } from './index.mock';
Expand Down Expand Up @@ -43,9 +43,14 @@ describe('Key rotation routes', () => {
});

it('correctly defines route.', () => {
expect(routeConfig.security).toEqual({
authz: {
requiredPrivileges: [ReservedPrivilegesSet.superuser],
},
});
expect(routeConfig.options).toEqual({
access: 'public',
tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'],
tags: ['oas-tag:saved objects'],
summary: `Rotate a key for encrypted saved objects`,
description: `If a saved object cannot be decrypted using the primary encryption key, Kibana attempts to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.
NOTE: Bulk key rotation can consume a considerable amount of resources and hence only user with a superuser role can trigger it.`,
Expand Down Expand Up @@ -96,7 +101,7 @@ describe('Key rotation routes', () => {

expect(config.options).toEqual({
access: 'internal',
tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'],
tags: ['oas-tag:saved objects'],
summary: `Rotate a key for encrypted saved objects`,
description: `If a saved object cannot be decrypted using the primary encryption key, Kibana attempts to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.
NOTE: Bulk key rotation can consume a considerable amount of resources and hence only user with a superuser role can trigger it.`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { schema } from '@kbn/config-schema';
import { ReservedPrivilegesSet } from '@kbn/core/server';

import type { RouteDefinitionParams } from '.';

Expand Down Expand Up @@ -39,9 +40,14 @@ export function defineKeyRotationRoutes({
type: schema.maybe(schema.string()),
}),
},
security: {
authz: {
requiredPrivileges: [ReservedPrivilegesSet.superuser],
},
},
options: {
tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'],
access: buildFlavor === 'serverless' ? 'internal' : 'public',
tags: ['oas-tag:saved objects'],
summary: `Rotate a key for encrypted saved objects`,
description: `If a saved object cannot be decrypted using the primary encryption key, Kibana attempts to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.
NOTE: Bulk key rotation can consume a considerable amount of resources and hence only user with a superuser role can trigger it.`,
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/security/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,14 @@ export const API_VERSIONS = {
},
},
};

/**
* Privileges that define the superuser role or the role equivalent to the superuser role.
*/
export const SUPERUSER_PRIVILEGES = {
kibana: ['*'],
elasticsearch: {
cluster: ['all'],
index: { '*': ['all'] },
},
};
Loading

0 comments on commit 598706c

Please sign in to comment.