mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Hardening] Kibana Feature API Privileges Names (#208067)](https://github.com/elastic/kibana/pull/208067) <!--- Backport version: 9.6.4 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Elena Shostak","email":"165678770+elena-shostak@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-02-03T14:22:29Z","message":"[Hardening] Kibana Feature API Privileges Names (#208067)\n\n## Summary\r\n\r\nAs part of our effort to harden API action definitions and enforce\r\nstandards this PR adds an utility `ApiPrivileges` class.\r\nIt is supposed to be used for both feature registration and API route\r\ndefinition to construct the privilege name.\r\n```ts\r\nplugins.features.registerKibanaFeature({\r\n privileges: {\r\n all: {\r\n app: [...],\r\n catalogue: [...],\r\n api: [ApiPrivileges.manage('subject_name')],\r\n ...\r\n },\r\n read: {\r\n ...\r\n api: [ApiPrivileges.read('subject_name')],\r\n ...\r\n },\r\n },\r\n})\r\n....\r\n\r\n// route definition\r\nrouter.get(\r\n {\r\n path: 'api_path',\r\n security: {\r\n authz: {\r\n requiredPrivileges: [ApiPrivileges.manage('subject_name')],\r\n },\r\n },\r\n },\r\n async (ctx, req, res) => {}\r\n);\r\n```\r\n\r\n`require_kibana_feature_privileges_naming` eslint rule has been added to\r\nshow warning if the API privilege name doesn't satisfy the naming\r\nconvention.\r\n\r\n### Naming convention\r\n\r\n- API privilege should start with valid `ApiOperation`: `manage`,\r\n`read`, `update`, `delete`, `create`\r\n- API privilege should use `_` as separator\r\n\r\n❌ `read-entity-a`\r\n❌ `delete_entity-a`\r\n❌ `entity_manage`\r\n✅ `read_entity_a`\r\n✅ `delete_entity_a`\r\n✅ `manage_entity`\r\n\r\n> [!IMPORTANT] \r\n> Serverless ZDT update scenario:\r\n>\r\n> - version N has an endpoint protected with the `old_privilege_read`.\r\n> - version N+1 has the same endpoint protected with a new\r\n`read_privilege`.\r\n> \r\n> There might be a short period between the time the UI pod N+1 passes\r\nSO migrations and updates privileges and the time it's marked as\r\nready-to-handle-requests by k8s, and when UI pod N is terminated.\r\n>\r\n> After discussion with @legrego and @azasypkin we decided to ignore it\r\ndue to the perceived risk-to-cost ratio:\r\n> 1. The time window users might be affected is very narrow because we\r\nregister privileges late in the Kibana startup flow (e.g., after SO\r\nmigrations).\r\n> 2. The transient 403 errors users might get won't result in session\r\ntermination and shouldn't lead to data loss.\r\n> 3. The roll-out will be performed in batches over the course of\r\nmultiple weeks and implemented by different teams. This means the impact\r\nper release shouldn't be significant.\r\n\r\n### Checklist\r\n\r\n- [x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n__Relates: https://github.com/elastic/kibana/issues/198716__\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"504510b92b0e92cbc173f0de517c506d2f54d536","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Security","release_note:skip","Feature:Hardening","backport:prev-minor","Team:Obs AI Assistant","backport:version","v9.1.0","v8.19.0"],"title":"[Hardening] Kibana Feature API Privileges Names","number":208067,"url":"https://github.com/elastic/kibana/pull/208067","mergeCommit":{"message":"[Hardening] Kibana Feature API Privileges Names (#208067)\n\n## Summary\r\n\r\nAs part of our effort to harden API action definitions and enforce\r\nstandards this PR adds an utility `ApiPrivileges` class.\r\nIt is supposed to be used for both feature registration and API route\r\ndefinition to construct the privilege name.\r\n```ts\r\nplugins.features.registerKibanaFeature({\r\n privileges: {\r\n all: {\r\n app: [...],\r\n catalogue: [...],\r\n api: [ApiPrivileges.manage('subject_name')],\r\n ...\r\n },\r\n read: {\r\n ...\r\n api: [ApiPrivileges.read('subject_name')],\r\n ...\r\n },\r\n },\r\n})\r\n....\r\n\r\n// route definition\r\nrouter.get(\r\n {\r\n path: 'api_path',\r\n security: {\r\n authz: {\r\n requiredPrivileges: [ApiPrivileges.manage('subject_name')],\r\n },\r\n },\r\n },\r\n async (ctx, req, res) => {}\r\n);\r\n```\r\n\r\n`require_kibana_feature_privileges_naming` eslint rule has been added to\r\nshow warning if the API privilege name doesn't satisfy the naming\r\nconvention.\r\n\r\n### Naming convention\r\n\r\n- API privilege should start with valid `ApiOperation`: `manage`,\r\n`read`, `update`, `delete`, `create`\r\n- API privilege should use `_` as separator\r\n\r\n❌ `read-entity-a`\r\n❌ `delete_entity-a`\r\n❌ `entity_manage`\r\n✅ `read_entity_a`\r\n✅ `delete_entity_a`\r\n✅ `manage_entity`\r\n\r\n> [!IMPORTANT] \r\n> Serverless ZDT update scenario:\r\n>\r\n> - version N has an endpoint protected with the `old_privilege_read`.\r\n> - version N+1 has the same endpoint protected with a new\r\n`read_privilege`.\r\n> \r\n> There might be a short period between the time the UI pod N+1 passes\r\nSO migrations and updates privileges and the time it's marked as\r\nready-to-handle-requests by k8s, and when UI pod N is terminated.\r\n>\r\n> After discussion with @legrego and @azasypkin we decided to ignore it\r\ndue to the perceived risk-to-cost ratio:\r\n> 1. The time window users might be affected is very narrow because we\r\nregister privileges late in the Kibana startup flow (e.g., after SO\r\nmigrations).\r\n> 2. The transient 403 errors users might get won't result in session\r\ntermination and shouldn't lead to data loss.\r\n> 3. The roll-out will be performed in batches over the course of\r\nmultiple weeks and implemented by different teams. This means the impact\r\nper release shouldn't be significant.\r\n\r\n### Checklist\r\n\r\n- [x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n__Relates: https://github.com/elastic/kibana/issues/198716__\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"504510b92b0e92cbc173f0de517c506d2f54d536"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/208067","number":208067,"mergeCommit":{"message":"[Hardening] Kibana Feature API Privileges Names (#208067)\n\n## Summary\r\n\r\nAs part of our effort to harden API action definitions and enforce\r\nstandards this PR adds an utility `ApiPrivileges` class.\r\nIt is supposed to be used for both feature registration and API route\r\ndefinition to construct the privilege name.\r\n```ts\r\nplugins.features.registerKibanaFeature({\r\n privileges: {\r\n all: {\r\n app: [...],\r\n catalogue: [...],\r\n api: [ApiPrivileges.manage('subject_name')],\r\n ...\r\n },\r\n read: {\r\n ...\r\n api: [ApiPrivileges.read('subject_name')],\r\n ...\r\n },\r\n },\r\n})\r\n....\r\n\r\n// route definition\r\nrouter.get(\r\n {\r\n path: 'api_path',\r\n security: {\r\n authz: {\r\n requiredPrivileges: [ApiPrivileges.manage('subject_name')],\r\n },\r\n },\r\n },\r\n async (ctx, req, res) => {}\r\n);\r\n```\r\n\r\n`require_kibana_feature_privileges_naming` eslint rule has been added to\r\nshow warning if the API privilege name doesn't satisfy the naming\r\nconvention.\r\n\r\n### Naming convention\r\n\r\n- API privilege should start with valid `ApiOperation`: `manage`,\r\n`read`, `update`, `delete`, `create`\r\n- API privilege should use `_` as separator\r\n\r\n❌ `read-entity-a`\r\n❌ `delete_entity-a`\r\n❌ `entity_manage`\r\n✅ `read_entity_a`\r\n✅ `delete_entity_a`\r\n✅ `manage_entity`\r\n\r\n> [!IMPORTANT] \r\n> Serverless ZDT update scenario:\r\n>\r\n> - version N has an endpoint protected with the `old_privilege_read`.\r\n> - version N+1 has the same endpoint protected with a new\r\n`read_privilege`.\r\n> \r\n> There might be a short period between the time the UI pod N+1 passes\r\nSO migrations and updates privileges and the time it's marked as\r\nready-to-handle-requests by k8s, and when UI pod N is terminated.\r\n>\r\n> After discussion with @legrego and @azasypkin we decided to ignore it\r\ndue to the perceived risk-to-cost ratio:\r\n> 1. The time window users might be affected is very narrow because we\r\nregister privileges late in the Kibana startup flow (e.g., after SO\r\nmigrations).\r\n> 2. The transient 403 errors users might get won't result in session\r\ntermination and shouldn't lead to data loss.\r\n> 3. The roll-out will be performed in batches over the course of\r\nmultiple weeks and implemented by different teams. This means the impact\r\nper release shouldn't be significant.\r\n\r\n### Checklist\r\n\r\n- [x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n__Relates: https://github.com/elastic/kibana/issues/198716__\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"504510b92b0e92cbc173f0de517c506d2f54d536"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"url":"https://github.com/elastic/kibana/pull/209315","number":209315,"branch":"9.0","state":"OPEN"}]}] BACKPORT-->
This commit is contained in:
parent
b1e03010ab
commit
6be0e84cb1
25 changed files with 451 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -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`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
"@kbn/analytics-collection-utils",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/core-node-server",
|
||||
"@kbn/security-plugin-types-server",
|
||||
"@kbn/core-user-profile-browser-mocks",
|
||||
"@kbn/security-plugin-types-common",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { isMinimalPrivilegeId, getMinimalPrivilegeId } from './src/privileges';
|
||||
export { isMinimalPrivilegeId, getMinimalPrivilegeId, ApiPrivileges } from './src/privileges';
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
}
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export { isMinimalPrivilegeId, getMinimalPrivilegeId } from './minimal_privileges';
|
||||
export { ApiPrivileges } from './api_privileges';
|
||||
|
|
|
@ -6,5 +6,7 @@
|
|||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["target/**/*"],
|
||||
"kbn_references": []
|
||||
"kbn_references": [
|
||||
"@kbn/security-plugin-types-common",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -47,3 +47,5 @@ export type {
|
|||
CategorizedApiKey,
|
||||
ApiKeyAggregations,
|
||||
} from './src/api_keys/api_key';
|
||||
|
||||
export { ApiOperation } from './src/authorization';
|
||||
|
|
|
@ -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',
|
||||
}
|
|
@ -17,3 +17,4 @@ export type {
|
|||
RoleRemoteIndexPrivilege,
|
||||
RoleRemoteClusterPrivilege,
|
||||
} from './role';
|
||||
export { ApiOperation } from './api';
|
||||
|
|
|
@ -89,4 +89,3 @@ export {
|
|||
getRestApiKeyWithKibanaPrivilegesSchema,
|
||||
} from './src/authentication';
|
||||
export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization';
|
||||
export { ApiOperation } from './src/authorization';
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -15,7 +15,6 @@ export type {
|
|||
SpaceActions,
|
||||
UIActions,
|
||||
} from './actions';
|
||||
export { ApiOperation } from './actions';
|
||||
export type { AuthorizationServiceSetup } from './authorization_service';
|
||||
export type {
|
||||
CheckPrivilegesOptions,
|
||||
|
|
|
@ -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')],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,5 +26,6 @@
|
|||
"@kbn/licensing-plugin",
|
||||
"@kbn/task-manager-plugin",
|
||||
"@kbn/inference-common",
|
||||
"@kbn/security-authorization-core-common",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"@kbn/server-route-repository-utils",
|
||||
"@kbn/inference-plugin",
|
||||
"@kbn/ai-assistant-icon",
|
||||
"@kbn/security-authorization-core-common"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue