Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[8.x] feat: allow plugins to deprecate and replace features and featu…
…re privileges (elastic#186800) (elastic#196204) # Backport This will backport the following commits from `main` to `8.x`: - [feat: allow plugins to deprecate and replace features and feature privileges (elastic#186800)](elastic#186800) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Aleh Zasypkin","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-14T19:40:59Z","message":"feat: allow plugins to deprecate and replace features and feature privileges (elastic#186800)\n\n## Summary\r\n\r\nThis change is the implementation of the `Kibana Privilege Migrations`\r\nproposal/RFC and provides a framework that allows developers to replace\r\nan existing feature with a new one that has the desired configuration\r\nwhile teaching the platform how the privileges of the deprecated feature\r\ncan be represented by non-deprecated ones. This approach avoids\r\nintroducing breaking changes for users who still rely on the deprecated\r\nprivileges in their existing roles and any automation.\r\n\r\nAmong the use cases the framework is supposed to handle, the most common\r\nare the following:\r\n\r\n* Changing a feature ID from `Alpha` to `Beta`\r\n* Splitting a feature `Alpha` into two features, `Beta` and `Gamma`\r\n* Moving a capability between privileges within a feature (top-level or\r\nsub-feature)\r\n* Consolidating capabilities across independent features\r\n\r\n## Scope\r\n\r\nThis PR includes only the core functionality proposed in the RFC and\r\nmost of the necessary guardrails (tests, early validations, etc.) to\r\nhelp engineers start planning and implementing their migrations as soon\r\nas possible. The following functionality will be added in follow-ups or\r\nonce we collect enough feedback:\r\n\r\n* Telemetry\r\n* Developer documentation\r\n* UI enhancements (highlighting roles with deprecated privileges and\r\nmanual migration actions)\r\n\r\n## Framework\r\n\r\nThe steps below use a scenario where a feature `Alpha` should be split\r\ninto two other features `Beta` and `Gamma` as an example.\r\n\r\n### Step 1: Create new features with the desired privileges\r\n\r\nFirst of all, define new feature or features with the desired\r\nconfiguration as you'd do before. There are no constraints here.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_beta',\r\n name: 'Feature Beta',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_gamma',\r\n name: 'Feature Gamma',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n // Note that Feature Gamma, unlike Features Alpha and Beta doesn't provide any API access tags\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_2'] },\r\n ui: ['ui_read'],\r\n // Note that Feature Gamma, unlike Features Alpha and Beta doesn't provide any API access tags\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n\r\n</details>\r\n\r\n### Step 2: Mark existing feature as deprecated\r\n\r\nOnce a feature is marked as deprecated, it should essentially be treated\r\nas frozen for backward compatibility reasons. Deprecated features will\r\nno longer be available through the Kibana role management UI and will be\r\nreplaced with non-deprecated privileges.\r\n\r\nDeprecated privileges will still be accepted if the role is created or\r\nupdated via the Kibana role management APIs to avoid disrupting existing\r\nuser automation.\r\n\r\nTo avoid breaking existing roles that reference privileges provided by\r\nthe deprecated features, Kibana will continue registering these\r\nprivileges as Elasticsearch application privileges.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n // This is a new `KibanaFeature` property available during feature registration.\r\n deprecated: {\r\n // User-facing justification for privilege deprecation that we can display\r\n // to the user when we ask them to perform role migration.\r\n notice: i18n.translate('xpack.security...', {\r\n defaultMessage: \"Feature Alpha is deprecated, refer to {link}...\",\r\n values: { link: docLinks.links.security.deprecatedFeatureAlpha },\r\n })\r\n },\r\n // Feature id should stay unchanged, and it's not possible to reuse it.\r\n id: 'feature_alpha',\r\n name: 'Feature Alpha (DEPRECATED)',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1', 'saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1', 'saved_object_2'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n</details>\r\n\r\n### Step 3: Map deprecated feature’s privileges to the privileges of the\r\nnon-deprecated features\r\n\r\nThe important requirement for a successful migration from a deprecated\r\nfeature to a new feature or features is that it should be possible to\r\nexpress **any combination** of the deprecated feature and sub-feature\r\nprivileges with the feature or sub-feature privileges of non-deprecated\r\nfeatures. This way, while editing a role with deprecated feature\r\nprivileges in the UI, the admin will be interacting with new privileges\r\nas if they were creating a new role from scratch, maintaining\r\nconsistency.\r\n\r\nThe relationship between the privileges of the deprecated feature and\r\nthe privileges of the features that are supposed to replace them is\r\nexpressed with a new `replacedBy` property available on the privileges\r\nof the deprecated feature.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n // This is a new `KibanaFeature` property available during feature registration.\r\n deprecated: {\r\n // User-facing justification for privilege deprecation that we can display\r\n // to the user when we ask them to perform role migration.\r\n notice: i18n.translate('xpack.security...', {\r\n defaultMessage: \"Feature Alpha is deprecated, refer to {link}...\",\r\n values: { link: docLinks.links.security.deprecatedFeatureAlpha },\r\n })\r\n },\r\n // Feature id should stay unchanged, and it's not possible to reuse it.\r\n id: 'feature_alpha',\r\n name: 'Feature Alpha (DEPRECATED)',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1', 'saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['all'] },\r\n { feature: 'feature_gamma', privileges: ['all'] },\r\n ],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1', 'saved_object_2'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['read'] },\r\n { feature: 'feature_gamma', privileges: ['read'] },\r\n\t],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n\r\n</details>\r\n\r\n### Step 4: Adjust the code to rely only on new, non-deprecated features\r\n\r\nSpecial care should be taken if the replacement privileges cannot reuse\r\nthe API access tags from the deprecated privileges and introduce new\r\ntags that will be applied to the same API endpoints. In this case,\r\ndevelopers should replace the API access tags of the deprecated\r\nprivileges with the corresponding tags provided by the replacement\r\nprivileges. This is necessary because API endpoints can only be accessed\r\nif the user privileges cover all the tags listed in the API endpoint\r\ndefinition, and without these changes, existing roles referencing\r\ndeprecated privileges won’t be able to access those endpoints.\r\n\r\nThe UI capabilities are handled slightly differently because they are\r\nalways prefixed with the feature ID. When migrating to new features with\r\nnew IDs, the code that interacts with UI capabilities will be updated to\r\nuse these new feature IDs.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\n// BEFORE deprecation/migration\r\n// 1. Feature Alpha defition (not deprecated yet)\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_alpha',\r\n privileges: {\r\n all: {\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 2. Route protected by `all` privilege of the Feature Alpha\r\nrouter.post(\r\n { path: '/api/domain/my_api', options: { tags: ['access:api_all'] } },\r\n async (_context, request, response) => {}\r\n);\r\n\r\n// AFTER deprecation/migration\r\n// 1. Feature Alpha defition (deprecated, with updated API tags)\r\ndeps.features.registerKibanaFeature({\r\n deprecated: …,\r\n id: 'feature_alpha',\r\n privileges: {\r\n all: {\r\n api: ['api_all_v2'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['all'] },\r\n ],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 2. Feature Beta defition (new)\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_beta',\r\n privileges: {\r\n all: {\r\n api: ['api_all_v2'],\r\n … omitted for brevity …\r\n }\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 3. Route protected by `all` privilege of the Feature Alpha OR Feature Beta\r\nrouter.post(\r\n { path: '/api/domain/my_api', options: { tags: ['access:api_all_v2'] } },\r\n async (_context, request, response) => {}\r\n);\r\n\r\n----\r\n\r\n// ❌ Old client-side code (supports only deprecated privileges)\r\nif (capabilities.feature_alpha.ui_all) {\r\n … omitted for brevity …\r\n}\r\n\r\n// ✅ New client-side code (will work for **both** new and deprecated privileges)\r\nif (capabilities.feature_beta.ui_all) {\r\n … omitted for brevity …\r\n}\r\n```\r\n</details>\r\n\r\n## How to test\r\n\r\nThe code introduces a set of API integration tests that are designed to\r\nvalidate whether the privilege mapping between deprecated and\r\nreplacement privileges maintains backward compatibility.\r\n\r\nYou can run the test server with the following config to register a\r\nnumber of [example deprecated\r\nfeatures](https://github.com/elastic/kibana/pull/186800/files#diff-d887981d43bbe30cda039340b906b0fa7649ba80230be4de8eda326036f10f6fR20-R49)(`x-pack/test/security_api_integration/plugins/features_provider/server/index.ts`)\r\nand the features that replace them, to see the framework in action:\r\n\r\n```bash\r\nnode scripts/functional_tests_server.js --config x-pack/test/security_api_integration/features.config.ts\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <[email protected]>","sha":"cb2112cae51d5f69b9e47ebfde66cfacb2a6719b","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Security","release_note:skip","Feature:Security/Authorization","v9.0.0","backport:prev-minor","v8.16.0"],"number":186800,"url":"https://github.com/elastic/kibana/pull/186800","mergeCommit":{"message":"feat: allow plugins to deprecate and replace features and feature privileges (elastic#186800)\n\n## Summary\r\n\r\nThis change is the implementation of the `Kibana Privilege Migrations`\r\nproposal/RFC and provides a framework that allows developers to replace\r\nan existing feature with a new one that has the desired configuration\r\nwhile teaching the platform how the privileges of the deprecated feature\r\ncan be represented by non-deprecated ones. This approach avoids\r\nintroducing breaking changes for users who still rely on the deprecated\r\nprivileges in their existing roles and any automation.\r\n\r\nAmong the use cases the framework is supposed to handle, the most common\r\nare the following:\r\n\r\n* Changing a feature ID from `Alpha` to `Beta`\r\n* Splitting a feature `Alpha` into two features, `Beta` and `Gamma`\r\n* Moving a capability between privileges within a feature (top-level or\r\nsub-feature)\r\n* Consolidating capabilities across independent features\r\n\r\n## Scope\r\n\r\nThis PR includes only the core functionality proposed in the RFC and\r\nmost of the necessary guardrails (tests, early validations, etc.) to\r\nhelp engineers start planning and implementing their migrations as soon\r\nas possible. The following functionality will be added in follow-ups or\r\nonce we collect enough feedback:\r\n\r\n* Telemetry\r\n* Developer documentation\r\n* UI enhancements (highlighting roles with deprecated privileges and\r\nmanual migration actions)\r\n\r\n## Framework\r\n\r\nThe steps below use a scenario where a feature `Alpha` should be split\r\ninto two other features `Beta` and `Gamma` as an example.\r\n\r\n### Step 1: Create new features with the desired privileges\r\n\r\nFirst of all, define new feature or features with the desired\r\nconfiguration as you'd do before. There are no constraints here.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_beta',\r\n name: 'Feature Beta',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_gamma',\r\n name: 'Feature Gamma',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n // Note that Feature Gamma, unlike Features Alpha and Beta doesn't provide any API access tags\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_2'] },\r\n ui: ['ui_read'],\r\n // Note that Feature Gamma, unlike Features Alpha and Beta doesn't provide any API access tags\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n\r\n</details>\r\n\r\n### Step 2: Mark existing feature as deprecated\r\n\r\nOnce a feature is marked as deprecated, it should essentially be treated\r\nas frozen for backward compatibility reasons. Deprecated features will\r\nno longer be available through the Kibana role management UI and will be\r\nreplaced with non-deprecated privileges.\r\n\r\nDeprecated privileges will still be accepted if the role is created or\r\nupdated via the Kibana role management APIs to avoid disrupting existing\r\nuser automation.\r\n\r\nTo avoid breaking existing roles that reference privileges provided by\r\nthe deprecated features, Kibana will continue registering these\r\nprivileges as Elasticsearch application privileges.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n // This is a new `KibanaFeature` property available during feature registration.\r\n deprecated: {\r\n // User-facing justification for privilege deprecation that we can display\r\n // to the user when we ask them to perform role migration.\r\n notice: i18n.translate('xpack.security...', {\r\n defaultMessage: \"Feature Alpha is deprecated, refer to {link}...\",\r\n values: { link: docLinks.links.security.deprecatedFeatureAlpha },\r\n })\r\n },\r\n // Feature id should stay unchanged, and it's not possible to reuse it.\r\n id: 'feature_alpha',\r\n name: 'Feature Alpha (DEPRECATED)',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1', 'saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1', 'saved_object_2'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n</details>\r\n\r\n### Step 3: Map deprecated feature’s privileges to the privileges of the\r\nnon-deprecated features\r\n\r\nThe important requirement for a successful migration from a deprecated\r\nfeature to a new feature or features is that it should be possible to\r\nexpress **any combination** of the deprecated feature and sub-feature\r\nprivileges with the feature or sub-feature privileges of non-deprecated\r\nfeatures. This way, while editing a role with deprecated feature\r\nprivileges in the UI, the admin will be interacting with new privileges\r\nas if they were creating a new role from scratch, maintaining\r\nconsistency.\r\n\r\nThe relationship between the privileges of the deprecated feature and\r\nthe privileges of the features that are supposed to replace them is\r\nexpressed with a new `replacedBy` property available on the privileges\r\nof the deprecated feature.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n // This is a new `KibanaFeature` property available during feature registration.\r\n deprecated: {\r\n // User-facing justification for privilege deprecation that we can display\r\n // to the user when we ask them to perform role migration.\r\n notice: i18n.translate('xpack.security...', {\r\n defaultMessage: \"Feature Alpha is deprecated, refer to {link}...\",\r\n values: { link: docLinks.links.security.deprecatedFeatureAlpha },\r\n })\r\n },\r\n // Feature id should stay unchanged, and it's not possible to reuse it.\r\n id: 'feature_alpha',\r\n name: 'Feature Alpha (DEPRECATED)',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1', 'saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['all'] },\r\n { feature: 'feature_gamma', privileges: ['all'] },\r\n ],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1', 'saved_object_2'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['read'] },\r\n { feature: 'feature_gamma', privileges: ['read'] },\r\n\t],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n\r\n</details>\r\n\r\n### Step 4: Adjust the code to rely only on new, non-deprecated features\r\n\r\nSpecial care should be taken if the replacement privileges cannot reuse\r\nthe API access tags from the deprecated privileges and introduce new\r\ntags that will be applied to the same API endpoints. In this case,\r\ndevelopers should replace the API access tags of the deprecated\r\nprivileges with the corresponding tags provided by the replacement\r\nprivileges. This is necessary because API endpoints can only be accessed\r\nif the user privileges cover all the tags listed in the API endpoint\r\ndefinition, and without these changes, existing roles referencing\r\ndeprecated privileges won’t be able to access those endpoints.\r\n\r\nThe UI capabilities are handled slightly differently because they are\r\nalways prefixed with the feature ID. When migrating to new features with\r\nnew IDs, the code that interacts with UI capabilities will be updated to\r\nuse these new feature IDs.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\n// BEFORE deprecation/migration\r\n// 1. Feature Alpha defition (not deprecated yet)\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_alpha',\r\n privileges: {\r\n all: {\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 2. Route protected by `all` privilege of the Feature Alpha\r\nrouter.post(\r\n { path: '/api/domain/my_api', options: { tags: ['access:api_all'] } },\r\n async (_context, request, response) => {}\r\n);\r\n\r\n// AFTER deprecation/migration\r\n// 1. Feature Alpha defition (deprecated, with updated API tags)\r\ndeps.features.registerKibanaFeature({\r\n deprecated: …,\r\n id: 'feature_alpha',\r\n privileges: {\r\n all: {\r\n api: ['api_all_v2'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['all'] },\r\n ],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 2. Feature Beta defition (new)\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_beta',\r\n privileges: {\r\n all: {\r\n api: ['api_all_v2'],\r\n … omitted for brevity …\r\n }\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 3. Route protected by `all` privilege of the Feature Alpha OR Feature Beta\r\nrouter.post(\r\n { path: '/api/domain/my_api', options: { tags: ['access:api_all_v2'] } },\r\n async (_context, request, response) => {}\r\n);\r\n\r\n----\r\n\r\n// ❌ Old client-side code (supports only deprecated privileges)\r\nif (capabilities.feature_alpha.ui_all) {\r\n … omitted for brevity …\r\n}\r\n\r\n// ✅ New client-side code (will work for **both** new and deprecated privileges)\r\nif (capabilities.feature_beta.ui_all) {\r\n … omitted for brevity …\r\n}\r\n```\r\n</details>\r\n\r\n## How to test\r\n\r\nThe code introduces a set of API integration tests that are designed to\r\nvalidate whether the privilege mapping between deprecated and\r\nreplacement privileges maintains backward compatibility.\r\n\r\nYou can run the test server with the following config to register a\r\nnumber of [example deprecated\r\nfeatures](https://github.com/elastic/kibana/pull/186800/files#diff-d887981d43bbe30cda039340b906b0fa7649ba80230be4de8eda326036f10f6fR20-R49)(`x-pack/test/security_api_integration/plugins/features_provider/server/index.ts`)\r\nand the features that replace them, to see the framework in action:\r\n\r\n```bash\r\nnode scripts/functional_tests_server.js --config x-pack/test/security_api_integration/features.config.ts\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <[email protected]>","sha":"cb2112cae51d5f69b9e47ebfde66cfacb2a6719b"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/186800","number":186800,"mergeCommit":{"message":"feat: allow plugins to deprecate and replace features and feature privileges (elastic#186800)\n\n## Summary\r\n\r\nThis change is the implementation of the `Kibana Privilege Migrations`\r\nproposal/RFC and provides a framework that allows developers to replace\r\nan existing feature with a new one that has the desired configuration\r\nwhile teaching the platform how the privileges of the deprecated feature\r\ncan be represented by non-deprecated ones. This approach avoids\r\nintroducing breaking changes for users who still rely on the deprecated\r\nprivileges in their existing roles and any automation.\r\n\r\nAmong the use cases the framework is supposed to handle, the most common\r\nare the following:\r\n\r\n* Changing a feature ID from `Alpha` to `Beta`\r\n* Splitting a feature `Alpha` into two features, `Beta` and `Gamma`\r\n* Moving a capability between privileges within a feature (top-level or\r\nsub-feature)\r\n* Consolidating capabilities across independent features\r\n\r\n## Scope\r\n\r\nThis PR includes only the core functionality proposed in the RFC and\r\nmost of the necessary guardrails (tests, early validations, etc.) to\r\nhelp engineers start planning and implementing their migrations as soon\r\nas possible. The following functionality will be added in follow-ups or\r\nonce we collect enough feedback:\r\n\r\n* Telemetry\r\n* Developer documentation\r\n* UI enhancements (highlighting roles with deprecated privileges and\r\nmanual migration actions)\r\n\r\n## Framework\r\n\r\nThe steps below use a scenario where a feature `Alpha` should be split\r\ninto two other features `Beta` and `Gamma` as an example.\r\n\r\n### Step 1: Create new features with the desired privileges\r\n\r\nFirst of all, define new feature or features with the desired\r\nconfiguration as you'd do before. There are no constraints here.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_beta',\r\n name: 'Feature Beta',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_gamma',\r\n name: 'Feature Gamma',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n // Note that Feature Gamma, unlike Features Alpha and Beta doesn't provide any API access tags\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_2'] },\r\n ui: ['ui_read'],\r\n // Note that Feature Gamma, unlike Features Alpha and Beta doesn't provide any API access tags\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n\r\n</details>\r\n\r\n### Step 2: Mark existing feature as deprecated\r\n\r\nOnce a feature is marked as deprecated, it should essentially be treated\r\nas frozen for backward compatibility reasons. Deprecated features will\r\nno longer be available through the Kibana role management UI and will be\r\nreplaced with non-deprecated privileges.\r\n\r\nDeprecated privileges will still be accepted if the role is created or\r\nupdated via the Kibana role management APIs to avoid disrupting existing\r\nuser automation.\r\n\r\nTo avoid breaking existing roles that reference privileges provided by\r\nthe deprecated features, Kibana will continue registering these\r\nprivileges as Elasticsearch application privileges.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n // This is a new `KibanaFeature` property available during feature registration.\r\n deprecated: {\r\n // User-facing justification for privilege deprecation that we can display\r\n // to the user when we ask them to perform role migration.\r\n notice: i18n.translate('xpack.security...', {\r\n defaultMessage: \"Feature Alpha is deprecated, refer to {link}...\",\r\n values: { link: docLinks.links.security.deprecatedFeatureAlpha },\r\n })\r\n },\r\n // Feature id should stay unchanged, and it's not possible to reuse it.\r\n id: 'feature_alpha',\r\n name: 'Feature Alpha (DEPRECATED)',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1', 'saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1', 'saved_object_2'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n</details>\r\n\r\n### Step 3: Map deprecated feature’s privileges to the privileges of the\r\nnon-deprecated features\r\n\r\nThe important requirement for a successful migration from a deprecated\r\nfeature to a new feature or features is that it should be possible to\r\nexpress **any combination** of the deprecated feature and sub-feature\r\nprivileges with the feature or sub-feature privileges of non-deprecated\r\nfeatures. This way, while editing a role with deprecated feature\r\nprivileges in the UI, the admin will be interacting with new privileges\r\nas if they were creating a new role from scratch, maintaining\r\nconsistency.\r\n\r\nThe relationship between the privileges of the deprecated feature and\r\nthe privileges of the features that are supposed to replace them is\r\nexpressed with a new `replacedBy` property available on the privileges\r\nof the deprecated feature.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\ndeps.features.registerKibanaFeature({\r\n // This is a new `KibanaFeature` property available during feature registration.\r\n deprecated: {\r\n // User-facing justification for privilege deprecation that we can display\r\n // to the user when we ask them to perform role migration.\r\n notice: i18n.translate('xpack.security...', {\r\n defaultMessage: \"Feature Alpha is deprecated, refer to {link}...\",\r\n values: { link: docLinks.links.security.deprecatedFeatureAlpha },\r\n })\r\n },\r\n // Feature id should stay unchanged, and it's not possible to reuse it.\r\n id: 'feature_alpha',\r\n name: 'Feature Alpha (DEPRECATED)',\r\n privileges: {\r\n all: {\r\n savedObject: { all: ['saved_object_1', 'saved_object_2'], read: [] },\r\n ui: ['ui_all'],\r\n api: ['api_all'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['all'] },\r\n { feature: 'feature_gamma', privileges: ['all'] },\r\n ],\r\n … omitted for brevity …\r\n },\r\n read: {\r\n savedObject: { all: [], read: ['saved_object_1', 'saved_object_2'] },\r\n ui: ['ui_read'],\r\n api: ['api_read'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['read'] },\r\n { feature: 'feature_gamma', privileges: ['read'] },\r\n\t],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n```\r\n\r\n</details>\r\n\r\n### Step 4: Adjust the code to rely only on new, non-deprecated features\r\n\r\nSpecial care should be taken if the replacement privileges cannot reuse\r\nthe API access tags from the deprecated privileges and introduce new\r\ntags that will be applied to the same API endpoints. In this case,\r\ndevelopers should replace the API access tags of the deprecated\r\nprivileges with the corresponding tags provided by the replacement\r\nprivileges. This is necessary because API endpoints can only be accessed\r\nif the user privileges cover all the tags listed in the API endpoint\r\ndefinition, and without these changes, existing roles referencing\r\ndeprecated privileges won’t be able to access those endpoints.\r\n\r\nThe UI capabilities are handled slightly differently because they are\r\nalways prefixed with the feature ID. When migrating to new features with\r\nnew IDs, the code that interacts with UI capabilities will be updated to\r\nuse these new feature IDs.\r\n\r\n<details>\r\n\r\n<summary>Click to see the code</summary>\r\n\r\n```ts\r\n// BEFORE deprecation/migration\r\n// 1. Feature Alpha defition (not deprecated yet)\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_alpha',\r\n privileges: {\r\n all: {\r\n api: ['api_all'],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 2. Route protected by `all` privilege of the Feature Alpha\r\nrouter.post(\r\n { path: '/api/domain/my_api', options: { tags: ['access:api_all'] } },\r\n async (_context, request, response) => {}\r\n);\r\n\r\n// AFTER deprecation/migration\r\n// 1. Feature Alpha defition (deprecated, with updated API tags)\r\ndeps.features.registerKibanaFeature({\r\n deprecated: …,\r\n id: 'feature_alpha',\r\n privileges: {\r\n all: {\r\n api: ['api_all_v2'],\r\n replacedBy: [\r\n { feature: 'feature_beta', privileges: ['all'] },\r\n ],\r\n … omitted for brevity …\r\n },\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 2. Feature Beta defition (new)\r\ndeps.features.registerKibanaFeature({\r\n id: 'feature_beta',\r\n privileges: {\r\n all: {\r\n api: ['api_all_v2'],\r\n … omitted for brevity …\r\n }\r\n },\r\n … omitted for brevity …\r\n});\r\n\r\n// 3. Route protected by `all` privilege of the Feature Alpha OR Feature Beta\r\nrouter.post(\r\n { path: '/api/domain/my_api', options: { tags: ['access:api_all_v2'] } },\r\n async (_context, request, response) => {}\r\n);\r\n\r\n----\r\n\r\n// ❌ Old client-side code (supports only deprecated privileges)\r\nif (capabilities.feature_alpha.ui_all) {\r\n … omitted for brevity …\r\n}\r\n\r\n// ✅ New client-side code (will work for **both** new and deprecated privileges)\r\nif (capabilities.feature_beta.ui_all) {\r\n … omitted for brevity …\r\n}\r\n```\r\n</details>\r\n\r\n## How to test\r\n\r\nThe code introduces a set of API integration tests that are designed to\r\nvalidate whether the privilege mapping between deprecated and\r\nreplacement privileges maintains backward compatibility.\r\n\r\nYou can run the test server with the following config to register a\r\nnumber of [example deprecated\r\nfeatures](https://github.com/elastic/kibana/pull/186800/files#diff-d887981d43bbe30cda039340b906b0fa7649ba80230be4de8eda326036f10f6fR20-R49)(`x-pack/test/security_api_integration/plugins/features_provider/server/index.ts`)\r\nand the features that replace them, to see the framework in action:\r\n\r\n```bash\r\nnode scripts/functional_tests_server.js --config x-pack/test/security_api_integration/features.config.ts\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <[email protected]>","sha":"cb2112cae51d5f69b9e47ebfde66cfacb2a6719b"}},{"branch":"8.x","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
- Loading branch information