From 504510b92b0e92cbc173f0de517c506d2f54d536 Mon Sep 17 00:00:00 2001 From: Elena Shostak <165678770+elena-shostak@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:22:29 +0100 Subject: [PATCH] [Hardening] Kibana Feature API Privileges Names (#208067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary As part of our effort to harden API action definitions and enforce standards this PR adds an utility `ApiPrivileges` class. It is supposed to be used for both feature registration and API route definition to construct the privilege name. ```ts plugins.features.registerKibanaFeature({ privileges: { all: { app: [...], catalogue: [...], api: [ApiPrivileges.manage('subject_name')], ... }, read: { ... api: [ApiPrivileges.read('subject_name')], ... }, }, }) .... // route definition router.get( { path: 'api_path', security: { authz: { requiredPrivileges: [ApiPrivileges.manage('subject_name')], }, }, }, async (ctx, req, res) => {} ); ``` `require_kibana_feature_privileges_naming` eslint rule has been added to show warning if the API privilege name doesn't satisfy the naming convention. ### Naming convention - API privilege should start with valid `ApiOperation`: `manage`, `read`, `update`, `delete`, `create` - API privilege should use `_` as separator ❌ `read-entity-a` ❌ `delete_entity-a` ❌ `entity_manage` ✅ `read_entity_a` ✅ `delete_entity_a` ✅ `manage_entity` > [!IMPORTANT] > Serverless ZDT update scenario: > > - version N has an endpoint protected with the `old_privilege_read`. > - version N+1 has the same endpoint protected with a new `read_privilege`. > > There might be a short period between the time the UI pod N+1 passes SO migrations and updates privileges and the time it's marked as ready-to-handle-requests by k8s, and when UI pod N is terminated. > > After discussion with @legrego and @azasypkin we decided to ignore it due to the perceived risk-to-cost ratio: > 1. The time window users might be affected is very narrow because we register privileges late in the Kibana startup flow (e.g., after SO migrations). > 2. The transient 403 errors users might get won't result in session termination and shouldn't lead to data loss. > 3. The roll-out will be performed in batches over the course of multiple weeks and implemented by different teams. This means the impact per release shouldn't be significant. ### Checklist - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 __Relates: https://github.com/elastic/kibana/issues/198716__ --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- dev_docs/key_concepts/api_authorization.mdx | 17 ++ packages/kbn-eslint-config/.eslintrc.js | 1 + packages/kbn-eslint-plugin-eslint/index.js | 1 + ...equire_kibana_feature_privileges_naming.js | 223 ++++++++++++++++++ ...e_kibana_feature_privileges_naming.test.js | 140 +++++++++++ .../server/routes/telemetry_usage_stats.ts | 2 +- .../plugins/shared/telemetry/tsconfig.json | 2 +- .../authorization_core/src/actions/api.ts | 2 +- .../src/privileges/privileges.test.ts | 2 +- .../src/privileges/privileges.ts | 2 +- .../authorization_core_common/index.ts | 2 +- .../src/privileges/api_privileges.ts | 29 +++ .../src/privileges/index.ts | 1 + .../authorization_core_common/tsconfig.json | 4 +- .../security/plugin_types_common/index.ts | 2 + .../src/authorization/api.ts | 13 + .../src/authorization/index.ts | 1 + .../security/plugin_types_server/index.ts | 1 - .../src/authorization/actions/api.ts | 10 +- .../src/authorization/actions/index.ts | 1 - .../src/authorization/index.ts | 1 - .../server/routes/installation.ts | 7 +- .../ai_infra/product_doc_base/tsconfig.json | 1 + .../server/plugin.ts | 7 +- .../observability_ai_assistant/tsconfig.json | 3 +- .../lib/setup/get_has_setup_privileges.ts | 2 +- .../plugins/profiling/tsconfig.json | 2 +- 27 files changed, 454 insertions(+), 25 deletions(-) create mode 100644 packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.js create mode 100644 packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.test.js create mode 100644 x-pack/platform/packages/private/security/authorization_core_common/src/privileges/api_privileges.ts create mode 100644 x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/api.ts diff --git a/dev_docs/key_concepts/api_authorization.mdx b/dev_docs/key_concepts/api_authorization.mdx index 5615a20d0f4b5..a2ffe5c98bef8 100644 --- a/dev_docs/key_concepts/api_authorization.mdx +++ b/dev_docs/key_concepts/api_authorization.mdx @@ -102,6 +102,23 @@ router.get({ }, handler); ``` +### Naming conventions for privileges +1. **Privilege should start with a valid `ApiOperation`**: + - **Valid operations**: `manage`, `read`, `update`, `delete`, `create`. + - Use the corresponding methods from the `ApiPrivileges` utility class: `ApiPrivileges.manage`, `ApiPrivileges.read`, etc. +2. **Use `_` as the separator** between the operation and the subject. + +**Examples**: +Incorrect privilege names ❌ +- `read-entity-a`: Uses `-` instead of `_`. +- `delete_entity-a`: Mixes `_` and `-`. +- `entity_manage`: Places the subject name before the operation. + +Correct privilege names ✅ +- `read_entity_a` +- `delete_entity_a` +- `manage_entity` + ### Configuring operator and superuser privileges We have two special predefined privilege sets that can be used in security configuration: 1. Operator diff --git a/packages/kbn-eslint-config/.eslintrc.js b/packages/kbn-eslint-config/.eslintrc.js index f5e44037ea0a6..4e81270da9367 100644 --- a/packages/kbn-eslint-config/.eslintrc.js +++ b/packages/kbn-eslint-config/.eslintrc.js @@ -322,6 +322,7 @@ module.exports = { '@kbn/eslint/no_async_promise_body': 'error', '@kbn/eslint/no_async_foreach': 'error', '@kbn/eslint/no_deprecated_authz_config': 'error', + '@kbn/eslint/require_kibana_feature_privileges_naming': 'warn', '@kbn/eslint/no_trailing_import_slash': 'error', '@kbn/eslint/no_constructor_args_in_property_initializers': 'error', '@kbn/eslint/no_this_in_property_initializers': 'error', diff --git a/packages/kbn-eslint-plugin-eslint/index.js b/packages/kbn-eslint-plugin-eslint/index.js index 8c52f916ec206..2eeb718ad1602 100644 --- a/packages/kbn-eslint-plugin-eslint/index.js +++ b/packages/kbn-eslint-plugin-eslint/index.js @@ -21,5 +21,6 @@ module.exports = { no_unsafe_console: require('./rules/no_unsafe_console'), no_unsafe_hash: require('./rules/no_unsafe_hash'), no_deprecated_authz_config: require('./rules/no_deprecated_authz_config'), + require_kibana_feature_privileges_naming: require('./rules/require_kibana_feature_privileges_naming'), }, }; diff --git a/packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.js b/packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.js new file mode 100644 index 0000000000000..9513b2b3f280f --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.js @@ -0,0 +1,223 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +const ts = require('typescript'); +const path = require('path'); + +function getImportedVariableValue(context, name, propertyName) { + const parent = context + .getAncestors() + .find((ancestor) => ['BlockStatement', 'Program'].includes(ancestor.type)); + + if (!parent) return; + + const importDeclaration = parent.body.find( + (statement) => + statement.type === 'ImportDeclaration' && + statement.specifiers.some((specifier) => specifier.local.name === name) + ); + + if (!importDeclaration) return; + + const absoluteImportPath = require.resolve(importDeclaration.source.value, { + paths: [path.dirname(context.getFilename())], + }); + + const program = ts.createProgram([absoluteImportPath], {}); + const sourceFile = program.getSourceFile(absoluteImportPath); + + if (!sourceFile) return null; + + const checker = program.getTypeChecker(); + const symbols = checker.getExportsOfModule(sourceFile.symbol); + const symbol = symbols.find((s) => s.name === name); + + if (!symbol) return null; + + if (propertyName) { + const currentSymbol = checker.getTypeOfSymbolAtLocation(symbol, sourceFile); + const property = currentSymbol.getProperty(propertyName); + + if (ts.isStringLiteral(property.valueDeclaration.initializer)) { + return property.valueDeclaration.initializer.text; + } + + return null; + } + + const initializer = symbol?.valueDeclaration?.initializer; + + if (ts.isStringLiteral(initializer)) { + return initializer.text; + } + + return null; +} + +function validatePrivilegesNode(context, privilegesNode, scopedVariables) { + ['all', 'read'].forEach((privilegeType) => { + const privilege = privilegesNode.value.properties.find( + (prop) => + prop.key && prop.key.name === privilegeType && prop.value.type === 'ObjectExpression' + ); + + if (!privilege) return; + + const apiProperty = privilege.value.properties.find( + (prop) => prop.key && prop.key.name === 'api' && prop.value.type === 'ArrayExpression' + ); + + if (!apiProperty) return; + + apiProperty.value.elements.forEach((element) => { + let valueToCheck = null; + + if (element.type === 'Literal' && typeof element.value === 'string') { + valueToCheck = element.value; + } else if (element.type === 'Identifier') { + valueToCheck = scopedVariables.has(element.name) + ? scopedVariables.get(element.name) + : getImportedVariableValue(context, element.name); + } else if (element.type === 'MemberExpression') { + valueToCheck = getImportedVariableValue( + context, + element.object.name, + element.property.name + ); + } + + if (valueToCheck) { + const isValid = /^(manage|create|update|delete|read)/.test(valueToCheck); + const usesValidSeparator = /^[a-z0-9_]+$/.test(valueToCheck); + let method = 'manage'; + + if (valueToCheck.includes('read')) { + method = 'read'; + } + + if (valueToCheck.includes('create') || valueToCheck.includes('copy')) { + method = 'create'; + } + + if (valueToCheck.includes('delete')) { + method = 'delete'; + } + + if (valueToCheck.includes('update')) { + method = 'update'; + } + + if (!isValid) { + return context.report({ + node: element, + message: `API privilege '${valueToCheck}' should start with [manage|create|update|delete|read] or use ApiPrivileges.${method} instead`, + }); + } + + if (!usesValidSeparator) { + return context.report({ + node: element, + message: `API privilege '${valueToCheck}' should use '_' as a separator`, + }); + } + } + }); + }); +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Ensure API privileges in registerKibanaFeature call follow naming conventions', + category: 'Best Practices', + recommended: true, + }, + schema: [], + }, + + create(context) { + return { + CallExpression(node) { + const isRegisterKibanaFeatureCall = + node.callee.type === 'MemberExpression' && + node.callee.property.name === 'registerKibanaFeature' && + ((node.callee.object.type === 'MemberExpression' && + node.callee.object.property.name === 'features') || + node.callee.object.name === 'features'); + + if (!isRegisterKibanaFeatureCall) return; + + const scopedVariables = new Map(); + + const sourceCode = context.getSourceCode(); + + const parent = sourceCode + .getAncestors(node) + .find((ancestor) => ['BlockStatement', 'Program'].includes(ancestor.type)); + + if (parent) { + parent.body.forEach((statement) => { + if (statement.type === 'VariableDeclaration') { + statement.declarations.forEach((declaration) => { + if ( + declaration.id.type === 'Identifier' && + declaration.init && + declaration.init.type === 'Literal' && + typeof declaration.init.value === 'string' + ) { + scopedVariables.set(declaration.id.name, declaration.init.value); + } + }); + } + }); + } + + const [feature] = node.arguments; + if (feature?.type === 'ObjectExpression') { + const privilegesProperty = feature.properties.find( + (prop) => + prop.key && prop.key.name === 'privileges' && prop.value.type === 'ObjectExpression' + ); + + if (!privilegesProperty) return; + + return validatePrivilegesNode(context, privilegesProperty, scopedVariables); + } + }, + ExportNamedDeclaration(node) { + if ( + node.declaration?.type !== 'VariableDeclaration' || + !node.declaration.declarations?.length + ) { + return; + } + + node.declaration.declarations.forEach((declaration) => { + if (declaration.init && declaration.init.type === 'ObjectExpression') { + if ( + !['id', 'name', 'privileges', 'scope', 'category'].every((key) => + declaration.init.properties.find((prop) => prop.key?.name === key) + ) + ) { + return; + } + + const privilegesProperty = declaration.init.properties.find( + (prop) => + prop.key && prop.key.name === 'privileges' && prop.value.type === 'ObjectExpression' + ); + + validatePrivilegesNode(context, privilegesProperty, new Map()); + } + }); + }, + }; + }, +}; diff --git a/packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.test.js b/packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.test.js new file mode 100644 index 0000000000000..00bd71c85f059 --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/require_kibana_feature_privileges_naming.test.js @@ -0,0 +1,140 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +const { RuleTester } = require('eslint'); +const rule = require('./require_kibana_feature_privileges_naming'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018, + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run('@kbn/require_kibana_feature_privileges_naming', rule, { + valid: [ + { + code: ` + const privilege = "manage_users"; + plugins.features.registerKibanaFeature({ + privileges: { + all: { + api: [privilege, "create_logs", "read_logs"], + }, + }, + }); + `, + }, + { + code: ` + plugins.features.registerKibanaFeature({ + privileges: { + all: { + api: ["manage_logs", "create_entries"], + }, + }, + }); + `, + }, + { + code: ` + features.registerKibanaFeature({ + privileges: { + all: { + api: ["read_entries", "update_entries"], + }, + }, + }); + `, + }, + { + code: ` + const validPrivilege = "delete_users"; + const anotherValidPrivilege = "manage_permissions"; + plugins.features.registerKibanaFeature({ + privileges: { + all: { + api: [validPrivilege, anotherValidPrivilege], + }, + }, + }); + `, + }, + ], + invalid: [ + { + code: ` + plugins.features.registerKibanaFeature({ + privileges: { + all: { + api: ["incorrect_value", "manage_logs"], + }, + }, + }); + `, + errors: [ + { + message: `API privilege 'incorrect_value' should start with [manage|create|update|delete|read] or use ApiPrivileges.manage instead`, + }, + ], + }, + { + code: ` + features.registerKibanaFeature({ + privileges: { + all: { + api: ["entry_read", "create_logs"], + }, + }, + }); + `, + errors: [ + { + message: `API privilege 'entry_read' should start with [manage|create|update|delete|read] or use ApiPrivileges.read instead`, + }, + ], + }, + { + code: ` + features.registerKibanaFeature({ + privileges: { + all: { + api: ["read_entry-log", "create_logs"], + }, + }, + }); + `, + errors: [ + { + message: `API privilege 'read_entry-log' should use '_' as a separator`, + }, + ], + }, + { + code: ` + const privilege = 'users-manage'; + plugins.features.registerKibanaFeature({ + privileges: { + all: { + api: [privilege, "create_logs", "read_logs"], + }, + }, + }); + `, + errors: [ + { + message: `API privilege 'users-manage' should start with [manage|create|update|delete|read] or use ApiPrivileges.manage instead`, + }, + ], + }, + ], +}); diff --git a/src/platform/plugins/shared/telemetry/server/routes/telemetry_usage_stats.ts b/src/platform/plugins/shared/telemetry/server/routes/telemetry_usage_stats.ts index 28198c853e4b5..3adcd00d6a8c7 100644 --- a/src/platform/plugins/shared/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/platform/plugins/shared/telemetry/server/routes/telemetry_usage_stats.ts @@ -14,7 +14,7 @@ import type { StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; -import { ApiOperation } from '@kbn/security-plugin-types-server'; +import { ApiOperation } from '@kbn/security-plugin-types-common'; import { RequestHandler } from '@kbn/core-http-server'; import { FetchSnapshotTelemetry } from '../../common/routes'; import { UsageStatsBody, v2 } from '../../common/types'; diff --git a/src/platform/plugins/shared/telemetry/tsconfig.json b/src/platform/plugins/shared/telemetry/tsconfig.json index c43f8a55702c9..0f75fd44ef4a0 100644 --- a/src/platform/plugins/shared/telemetry/tsconfig.json +++ b/src/platform/plugins/shared/telemetry/tsconfig.json @@ -34,12 +34,12 @@ "@kbn/analytics-collection-utils", "@kbn/react-kibana-mount", "@kbn/core-node-server", - "@kbn/security-plugin-types-server", "@kbn/core-user-profile-browser-mocks", "@kbn/core-analytics-browser", "@kbn/core-analytics-server", "@kbn/core-elasticsearch-server", "@kbn/logging", + "@kbn/security-plugin-types-common", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/packages/private/security/authorization_core/src/actions/api.ts b/x-pack/platform/packages/private/security/authorization_core/src/actions/api.ts index d91bc1bd89669..7bd8b019af088 100644 --- a/x-pack/platform/packages/private/security/authorization_core/src/actions/api.ts +++ b/x-pack/platform/packages/private/security/authorization_core/src/actions/api.ts @@ -7,8 +7,8 @@ import { isString } from 'lodash'; +import { ApiOperation } from '@kbn/security-plugin-types-common'; import type { ApiActions as ApiActionsType } from '@kbn/security-plugin-types-server'; -import { ApiOperation } from '@kbn/security-plugin-types-server'; export class ApiActions implements ApiActionsType { private readonly prefix: string; diff --git a/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.test.ts b/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.test.ts index 04770495fb2cf..cbd6032761f1b 100644 --- a/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.test.ts +++ b/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.test.ts @@ -7,7 +7,7 @@ import { KibanaFeature } from '@kbn/features-plugin/server'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; -import { ApiOperation } from '@kbn/security-plugin-types-server'; +import { ApiOperation } from '@kbn/security-plugin-types-common'; import { getReplacedByForPrivilege, privilegesFactory } from './privileges'; import { licenseMock } from '../__fixtures__/licensing.mock'; diff --git a/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.ts b/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.ts index f266b2b9a7085..433f26785da84 100644 --- a/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.ts +++ b/x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.ts @@ -17,7 +17,7 @@ import { isMinimalPrivilegeId, } from '@kbn/security-authorization-core-common'; import type { RawKibanaPrivileges, SecurityLicense } from '@kbn/security-plugin-types-common'; -import { ApiOperation } from '@kbn/security-plugin-types-server'; +import { ApiOperation } from '@kbn/security-plugin-types-common'; import { featurePrivilegeBuilderFactory } from './feature_privilege_builder'; import type { Actions } from '../actions'; diff --git a/x-pack/platform/packages/private/security/authorization_core_common/index.ts b/x-pack/platform/packages/private/security/authorization_core_common/index.ts index 63fa40559fae2..3963bd5fe2577 100644 --- a/x-pack/platform/packages/private/security/authorization_core_common/index.ts +++ b/x-pack/platform/packages/private/security/authorization_core_common/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { isMinimalPrivilegeId, getMinimalPrivilegeId } from './src/privileges'; +export { isMinimalPrivilegeId, getMinimalPrivilegeId, ApiPrivileges } from './src/privileges'; diff --git a/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/api_privileges.ts b/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/api_privileges.ts new file mode 100644 index 0000000000000..7eb8001b9fab0 --- /dev/null +++ b/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/api_privileges.ts @@ -0,0 +1,29 @@ +/* + * 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 { ApiOperation } from '@kbn/security-plugin-types-common'; + +export class ApiPrivileges { + public static manage(subject: string) { + return `${ApiOperation.Manage}_${subject}`; + } + + public static read(subject: string) { + return `${ApiOperation.Read}_${subject}`; + } + + public static create(subject: string) { + return `${ApiOperation.Create}_${subject}`; + } + + public static update(subject: string) { + return `${ApiOperation.Update}_${subject}`; + } + + public static delete(subject: string) { + return `${ApiOperation.Delete}_${subject}`; + } +} diff --git a/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/index.ts b/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/index.ts index 01e05bfabde5c..3b678644f63be 100644 --- a/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/index.ts +++ b/x-pack/platform/packages/private/security/authorization_core_common/src/privileges/index.ts @@ -6,3 +6,4 @@ */ export { isMinimalPrivilegeId, getMinimalPrivilegeId } from './minimal_privileges'; +export { ApiPrivileges } from './api_privileges'; diff --git a/x-pack/platform/packages/private/security/authorization_core_common/tsconfig.json b/x-pack/platform/packages/private/security/authorization_core_common/tsconfig.json index 597c1b47cb03a..b7a95d84501e8 100644 --- a/x-pack/platform/packages/private/security/authorization_core_common/tsconfig.json +++ b/x-pack/platform/packages/private/security/authorization_core_common/tsconfig.json @@ -6,5 +6,7 @@ }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["target/**/*"], - "kbn_references": [] + "kbn_references": [ + "@kbn/security-plugin-types-common", + ] } diff --git a/x-pack/platform/packages/shared/security/plugin_types_common/index.ts b/x-pack/platform/packages/shared/security/plugin_types_common/index.ts index c435dae0baedc..c5210877f28ac 100644 --- a/x-pack/platform/packages/shared/security/plugin_types_common/index.ts +++ b/x-pack/platform/packages/shared/security/plugin_types_common/index.ts @@ -47,3 +47,5 @@ export type { CategorizedApiKey, ApiKeyAggregations, } from './src/api_keys/api_key'; + +export { ApiOperation } from './src/authorization'; diff --git a/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/api.ts b/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/api.ts new file mode 100644 index 0000000000000..dbb4f0bf0ba4d --- /dev/null +++ b/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/api.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ +export enum ApiOperation { + Read = 'read', + Create = 'create', + Update = 'update', + Delete = 'delete', + Manage = 'manage', +} diff --git a/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/index.ts b/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/index.ts index 4079bf81ee441..7cbcfd7261957 100644 --- a/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/index.ts +++ b/x-pack/platform/packages/shared/security/plugin_types_common/src/authorization/index.ts @@ -17,3 +17,4 @@ export type { RoleRemoteIndexPrivilege, RoleRemoteClusterPrivilege, } from './role'; +export { ApiOperation } from './api'; diff --git a/x-pack/platform/packages/shared/security/plugin_types_server/index.ts b/x-pack/platform/packages/shared/security/plugin_types_server/index.ts index 4f213fc6c9920..ac362ed3a4cf9 100644 --- a/x-pack/platform/packages/shared/security/plugin_types_server/index.ts +++ b/x-pack/platform/packages/shared/security/plugin_types_server/index.ts @@ -89,4 +89,3 @@ export { getRestApiKeyWithKibanaPrivilegesSchema, } from './src/authentication'; export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization'; -export { ApiOperation } from './src/authorization'; diff --git a/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/api.ts b/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/api.ts index 01fa535a1a0d5..e689e4c5937fd 100644 --- a/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/api.ts +++ b/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/api.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { ApiOperation } from '@kbn/security-plugin-types-common'; + export interface ApiActions { get(operation: ApiOperation, subject: string): string; @@ -14,11 +16,3 @@ export interface ApiActions { get(subject: string): string; actionFromRouteTag(routeTag: string): string; } - -export enum ApiOperation { - Read = 'read', - Create = 'create', - Update = 'update', - Delete = 'delete', - Manage = 'manage', -} diff --git a/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/index.ts b/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/index.ts index baed1cde4457e..6b3993423015f 100644 --- a/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/index.ts +++ b/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/actions/index.ts @@ -8,7 +8,6 @@ export type { Actions } from './actions'; export type { AlertingActions } from './alerting'; export type { ApiActions } from './api'; -export { ApiOperation } from './api'; export type { AppActions } from './app'; export type { CasesActions } from './cases'; export type { SavedObjectActions } from './saved_object'; diff --git a/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/index.ts b/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/index.ts index 0ffa0900fa2d3..8f9e2788753a3 100644 --- a/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/index.ts +++ b/x-pack/platform/packages/shared/security/plugin_types_server/src/authorization/index.ts @@ -15,7 +15,6 @@ export type { SpaceActions, UIActions, } from './actions'; -export { ApiOperation } from './actions'; export type { AuthorizationServiceSetup } from './authorization_service'; export type { CheckPrivilegesOptions, diff --git a/x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/routes/installation.ts b/x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/routes/installation.ts index 1e6b5545ebb4e..e10e74b1ffb07 100644 --- a/x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/routes/installation.ts +++ b/x-pack/platform/plugins/shared/ai_infra/product_doc_base/server/routes/installation.ts @@ -6,6 +6,7 @@ */ import type { IRouter } from '@kbn/core/server'; +import { ApiPrivileges } from '@kbn/security-authorization-core-common'; import { INSTALLATION_STATUS_API_PATH, INSTALL_ALL_API_PATH, @@ -32,7 +33,7 @@ export const registerInstallationRoutes = ({ }, security: { authz: { - requiredPrivileges: ['manage_llm_product_doc'], + requiredPrivileges: [ApiPrivileges.manage('llm_product_doc')], }, }, }, @@ -60,7 +61,7 @@ export const registerInstallationRoutes = ({ }, security: { authz: { - requiredPrivileges: ['manage_llm_product_doc'], + requiredPrivileges: [ApiPrivileges.manage('llm_product_doc')], }, }, }, @@ -93,7 +94,7 @@ export const registerInstallationRoutes = ({ }, security: { authz: { - requiredPrivileges: ['manage_llm_product_doc'], + requiredPrivileges: [ApiPrivileges.manage('llm_product_doc')], }, }, }, diff --git a/x-pack/platform/plugins/shared/ai_infra/product_doc_base/tsconfig.json b/x-pack/platform/plugins/shared/ai_infra/product_doc_base/tsconfig.json index 66b31885ea68b..bb592405ba8da 100644 --- a/x-pack/platform/plugins/shared/ai_infra/product_doc_base/tsconfig.json +++ b/x-pack/platform/plugins/shared/ai_infra/product_doc_base/tsconfig.json @@ -26,5 +26,6 @@ "@kbn/licensing-plugin", "@kbn/task-manager-plugin", "@kbn/inference-common", + "@kbn/security-authorization-core-common", ] } diff --git a/x-pack/platform/plugins/shared/observability_ai_assistant/server/plugin.ts b/x-pack/platform/plugins/shared/observability_ai_assistant/server/plugin.ts index 564cabf3ed1f5..bf61ed450f8d7 100644 --- a/x-pack/platform/plugins/shared/observability_ai_assistant/server/plugin.ts +++ b/x-pack/platform/plugins/shared/observability_ai_assistant/server/plugin.ts @@ -15,6 +15,7 @@ import { import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { KibanaFeatureScope } from '@kbn/features-plugin/common'; +import { ApiPrivileges } from '@kbn/security-authorization-core-common'; import { OBSERVABILITY_AI_ASSISTANT_FEATURE_ID } from '../common/feature'; import type { ObservabilityAIAssistantConfig } from './config'; import { registerServerRoutes } from './routes/register_routes'; @@ -72,7 +73,11 @@ export class ObservabilityAIAssistantPlugin privileges: { all: { app: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID, 'kibana'], - api: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID, 'ai_assistant', 'manage_llm_product_doc'], + api: [ + OBSERVABILITY_AI_ASSISTANT_FEATURE_ID, + 'ai_assistant', + ApiPrivileges.manage('llm_product_doc'), + ], catalogue: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID], savedObject: { all: [], diff --git a/x-pack/platform/plugins/shared/observability_ai_assistant/tsconfig.json b/x-pack/platform/plugins/shared/observability_ai_assistant/tsconfig.json index 823bc57019294..a43de61a375e0 100644 --- a/x-pack/platform/plugins/shared/observability_ai_assistant/tsconfig.json +++ b/x-pack/platform/plugins/shared/observability_ai_assistant/tsconfig.json @@ -50,7 +50,8 @@ "@kbn/core-lifecycle-server", "@kbn/server-route-repository-utils", "@kbn/inference-plugin", - "@kbn/ai-assistant-icon" + "@kbn/ai-assistant-icon", + "@kbn/security-authorization-core-common" ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/observability/plugins/profiling/server/lib/setup/get_has_setup_privileges.ts b/x-pack/solutions/observability/plugins/profiling/server/lib/setup/get_has_setup_privileges.ts index ad4a0879d9c88..bc9fe8d419589 100644 --- a/x-pack/solutions/observability/plugins/profiling/server/lib/setup/get_has_setup_privileges.ts +++ b/x-pack/solutions/observability/plugins/profiling/server/lib/setup/get_has_setup_privileges.ts @@ -7,7 +7,7 @@ import type { KibanaRequest } from '@kbn/core/server'; import { INTEGRATIONS_PLUGIN_ID, PLUGIN_ID as FLEET_PLUGIN_ID } from '@kbn/fleet-plugin/common'; -import { ApiOperation } from '@kbn/security-plugin-types-server'; +import { ApiOperation } from '@kbn/security-plugin-types-common'; import type { ProfilingPluginStartDeps } from '../../types'; export async function getHasSetupPrivileges({ diff --git a/x-pack/solutions/observability/plugins/profiling/tsconfig.json b/x-pack/solutions/observability/plugins/profiling/tsconfig.json index bcb0ced0afdc5..439fb920fa44f 100644 --- a/x-pack/solutions/observability/plugins/profiling/tsconfig.json +++ b/x-pack/solutions/observability/plugins/profiling/tsconfig.json @@ -55,7 +55,7 @@ "@kbn/deeplinks-observability", "@kbn/react-kibana-context-render", "@kbn/apm-data-access-plugin", - "@kbn/security-plugin-types-server" + "@kbn/security-plugin-types-common" // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json