mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 10:23:14 -04:00
## 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 <elasticmachine@users.noreply.github.com>
385 lines
15 KiB
Text
385 lines
15 KiB
Text
---
|
|
id: kibDevDocsSecurityAPIAuthorization
|
|
slug: /kibana-dev-docs/key-concepts/security-api-authorization
|
|
title: Kibana API authorization
|
|
description: This guide provides an overview of API authorization in Kibana.
|
|
date: 2024-10-04
|
|
tags: ['kibana', 'dev', 'contributor', 'security']
|
|
---
|
|
|
|
Authorization is an important aspect of API design. It must be considered for all endpoints, even those marked as `internal`. This guide explains how and when to apply authorization to your endpoints
|
|
|
|
Table of contents:
|
|
1. [API authorization](#api-authorization)
|
|
2. [[Deprecated] Adding API authorization with `access` tags](#deprecated-adding-api-authorization-with-access-tags)
|
|
- [Why not add `access` tags to all routes by default?](#why-not-add-access-tags-to-all-routes-by-default)
|
|
3. [Adding API authorization with `security` configuration](#adding-api-authorization-with-security-configuration)
|
|
- [Key features](#key-features)
|
|
- [Configuring authorization on routes](#configuring-authorization-on-routes)
|
|
- [Opting out of authorization for specific routes](#opting-out-of-authorization-for-specific-routes)
|
|
- [Classic router security configuration examples](#classic-router-security-configuration-examples)
|
|
- [Versioned router security configuration examples](#versioned-router-security-configuration-examples)
|
|
4. [Authorization response available in route handlers](#authorization-response-available-in-route-handlers)
|
|
5. [OpenAPI specification (OAS) documentation](#openapi-specification-oas-documentation)
|
|
6. [Migrating from `access` tags to `security` configuration](#migrating-from-access-tags-to-security-configuration)
|
|
7. [Questions?](#questions)
|
|
|
|
## API authorization
|
|
Kibana API routes do not have any authorization checks applied by default. This means that your APIs are accessible to anyone with valid credentials, regardless of their permissions. This includes users with no roles assigned.
|
|
This on its own is insufficient, and care must be taken to ensure that only authorized users can invoke your endpoints.
|
|
|
|
Kibana leverages <DocLink id="kibDevDocsSavedObjectsIntro" text="Saved Objects" /> for a majority of its persistence. The Saved Objects Service performs its own authorization checks, so if your API route is primarily a CRUD interface to Saved Objects, then your authorization needs are likely already met.
|
|
This is also true for derivatives of the Saved Objects Service, such as the Alerting and Cases services.
|
|
|
|
If your endpoint is not a CRUD interface to Saved Objects, or if your endpoint bypasses our built-in Saved Objects authorization checks, then you must ensure that only authorized users can invoke your endpoint.
|
|
This is **especially** important if your route does any of the following:
|
|
1. Performs non-insignificant processing, causing load on the Elasticsearch cluster or the Kibana server.
|
|
2. Calls Elasticsearch APIs using the internal `kibana_system` user.
|
|
3. Calls a third-party service.
|
|
4. Exposes any non-public information to the caller, such as system configuration or state, as part of the successful or even error response.
|
|
|
|
## [Deprecated] Adding API authorization with `access` tags
|
|
**Note**: `access` tags were deprecated in favour of `security` configuration.
|
|
|
|
`access` tags are used to restrict access to API routes. They are used to ensure that only users with the required privileges can access the route.
|
|
|
|
Example configuration:
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
options: {
|
|
tags: ['access:<privilege_1>', 'access:<privilege_2>'],
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
More information on adding `access` tags to your routes can be found temporarily in the [legacy documentation](https://www.elastic.co/guide/en/kibana/current/development-security.html#development-plugin-feature-registration)
|
|
|
|
### Why not add `access` tags to all routes by default?
|
|
Each authorization check that we perform involves a round-trip to Elasticsearch, so they are not as cheap as we'd like. Therefore, we want to keep the number of authorization checks we perform within a single route to a minimum.
|
|
Adding an `access` tag to routes that leverage the Saved Objects Service would be redundant in most cases, since the Saved Objects Service will be performing authorization anyway.
|
|
|
|
|
|
## Adding API authorization with `security` configuration
|
|
`KibanaRouteOptions` provides a security configuration at the route definition level, offering robust security configurations for both **Classic** and **Versioned** routes.
|
|
|
|
### Key features:
|
|
1. **Fine-grained control**:
|
|
- Define the exact privileges required to access the route.
|
|
- Use `requiredPrivileges` to specify privileges with support for complex rules:
|
|
- **AND rules** using `allRequired`: Requires all specified privileges for access.
|
|
- **OR rules** using `anyRequired`: Allows access if any one of the specified privileges is met.
|
|
- **Complex Nested Rules**: Combine both `allRequired` and `anyRequired` for advanced access rules.
|
|
2. **Explicit Opt-out**: Provide a reason for opting out of authorization to maintain transparency.
|
|
3. **Versioned Routes**: Define security configurations for different versions of the same route.
|
|
4. **Improved Documentation with OpenAPI (OAS)**: Automatically generated OAS documentation with the required privileges for each route.
|
|
5. **AuthzResult Object in Route Handlers**: Access the authorization response in route handlers to see which privileges were met.
|
|
|
|
|
|
### Configuring authorization on routes
|
|
**Before migration:**
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
options: {
|
|
tags: ['access:<privilege_1>', 'access:<privilege_2>'],
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
**After migration:**
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_1>', '<privilege_2>'],
|
|
},
|
|
},
|
|
...
|
|
}, 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
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: [ReservedPrivilegesSet.operator, '<privilege_2>'],
|
|
},
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
Operator privileges check is enforced only if operator privileges are enabled in the Elasticsearch configuration.
|
|
For more information on operator privileges, refer to the [Operator privileges documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/operator-privileges.html).
|
|
If operator privileges are disabled, we skip the check for it, so the only privilege checked from the example above is `<privilege_2>`.
|
|
Operator privileges cannot be used as standalone, it is required to explicitly specify additional privileges in the configuration to ensure that the route is protected even when operator privileges are disabled.
|
|
|
|
2. Superuser
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: [ReservedPrivilegesSet.superuser],
|
|
},
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
### Opting out of authorization for specific routes
|
|
**Before migration:**
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
**After migration:**
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
enabled: false,
|
|
reason: 'This route is opted out from authorization because ...',
|
|
},
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
### Classic router security configuration examples
|
|
|
|
**Example 1: All privileges required.**
|
|
Requires `<privilege_1>` AND `<privilege_2>` to access the route.
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_1>', '<privilege_2>'],
|
|
},
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
**Example 2: Any privileges required.**
|
|
Requires `<privilege_1>` OR `<privilege_2>` to access the route.
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: [{ anyRequired: ['<privilege_1>', '<privilege_2>'] }],
|
|
},
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
**Example 3: Complex configuration.**
|
|
Requires `<privilege_1>` AND `<privilege_2>` AND (`<privilege_3>` OR `<privilege_4>`) to access the route.
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: [{ allRequired: ['<privilege_1>', '<privilege_2>'], anyRequired: ['<privilege_3>', '<privilege_4>'] }],
|
|
},
|
|
},
|
|
...
|
|
}, handler);
|
|
```
|
|
|
|
### Versioned router security configuration examples
|
|
Different security configurations can be applied to each version when using the Versioned Router. This allows your authorization needs to evolve in lockstep with your API.
|
|
|
|
**Example 1: Default and custom version security.**
|
|
|
|
1. **Default configuration**: Applies to versions without specific authorization, requires `<privilege_1>`.
|
|
|
|
2. **Version 1**: Requires **both** `<privilege_1>` and `<privilege_2>` privileges.
|
|
|
|
3. **Version 2**: Inherits the default authorization configuration, requiring `<privilege_1>`.
|
|
|
|
```ts
|
|
router.versioned
|
|
.get({
|
|
path: '/internal/path',
|
|
access: 'internal',
|
|
// default security configuration, will be used for version unless overridden
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_1>'],
|
|
},
|
|
},
|
|
})
|
|
.addVersion({
|
|
version: '1',
|
|
validate: false,
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_1>', '<privilege_2>'],
|
|
},
|
|
},
|
|
}, handlerV1)
|
|
.addVersion({
|
|
version: '2',
|
|
validate: false,
|
|
}, handlerV2);
|
|
```
|
|
|
|
**Example 2: Multiple versions with different security requirements.**
|
|
1. **Default Configuration**: Applies to versions without specific authorization, requires `<privilege_1>`.
|
|
|
|
2. **Version 1**: Requires **both** `<privilege_1>` and `<privilege_2>` privileges.
|
|
|
|
3. **Version 2**: Requires `<privilege_3>` AND (`<privilege_1>` OR `<privilege_2>`).
|
|
|
|
4. **Version 3**: Requires only `<privilege_3>`.
|
|
|
|
```ts
|
|
router.versioned
|
|
.get({
|
|
path: '/internal/path',
|
|
access: 'internal',
|
|
// default security configuration, will be used for version unless overridden
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_1>'],
|
|
},
|
|
},
|
|
})
|
|
.addVersion({
|
|
version: '1',
|
|
validate: false,
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_1>', '<privilege_2>'],
|
|
},
|
|
},
|
|
}, handlerV1)
|
|
.addVersion({
|
|
version: '2',
|
|
validate: false,
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_3>', anyRequired: ['<privilege_1>', '<privilege_2>']],
|
|
},
|
|
},
|
|
}, handlerV2)
|
|
.addVersion({
|
|
version: '3',
|
|
validate: false,
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_3>'],
|
|
},
|
|
},
|
|
}, handlerV3);
|
|
```
|
|
|
|
## Authorization response available in route handlers
|
|
The `AuthzResult` object is available in route handlers, which provides information about the privileges granted to the caller.
|
|
For example, you have a route that requires `<privilege_3>` and ANY of the privileges `<privilege_1>` OR `<privilege_2>`:
|
|
```ts
|
|
router.get({
|
|
path: '/api/path',
|
|
security: {
|
|
authz: {
|
|
requiredPrivileges: ['<privilege_3>', { anyRequired: ['<privilege_1>', '<privilege_2>'] }],
|
|
},
|
|
},
|
|
...
|
|
}, (context, request, response) => {
|
|
// The authorization response is available in `request.authzResult`
|
|
// {
|
|
// "<privilege_3>": true,
|
|
// "<privilege_1>": true,
|
|
// "<privilege_2>": false
|
|
// }
|
|
});
|
|
```
|
|
|
|
## OpenAPI specification (OAS) documentation
|
|
Based on the security configuration defined in routes, OAS documentation will automatically generate and include description about the required privileges.
|
|
This makes it easy to view the security requirements of each endpoint in a standardized format, facilitating better understanding and usage by developers or teams consuming the API.
|
|
|
|
To check the OAS documentation for a specific API route and see its security details, you can use the following command:
|
|
```sh
|
|
GET /api/oas?pathStartsWith=/your/api/path
|
|
```
|
|
|
|
## Migrating from `access` tags to `security` configuration
|
|
We aim to use the same privileges that are currently defined with tags `access:<privilege_name>`.
|
|
To assist with this migration, we have created eslint rule `no_deprecated_authz_config`, that will automatically convert your `access` tags to the new `security` configuration.
|
|
It scans route definitions and converts `access` tags to the new `requiredPrivileges` configuration.
|
|
|
|
Note: The rule is disabled by default to avoid automatic migrations without an oversight. You can perform migrations by running:
|
|
|
|
**Migrate routes with defined authorization**
|
|
```sh
|
|
MIGRATE_DISABLED_AUTHZ=false MIGRATE_ENABLED_AUTHZ=true npx eslint --ext .ts --fix path/to/your/folder
|
|
```
|
|
|
|
**Migrate routes opted out from authorization**
|
|
```sh
|
|
MIGRATE_DISABLED_AUTHZ=true MIGRATE_ENABLED_AUTHZ=false npx eslint --ext .ts --fix path/to/your/folder
|
|
```
|
|
We encourage you to migrate routes that are opted out from authorization to new config and provide legitimate reason for disabled authorization.
|
|
It is better to migrate routes opted out from authorization iteratively and elaborate on the reasoning.
|
|
Routes without a compelling reason to opt-out of authorization should plan to introduce them as soon as possible.
|
|
|
|
**Migrate all routes**
|
|
```sh
|
|
MIGRATE_DISABLED_AUTHZ=true MIGRATE_ENABLED_AUTHZ=true npx eslint --ext .ts --fix path/to/your/folder
|
|
```
|
|
|
|
**How to migrate if you have an utility function for route creation?**
|
|
If you have utility function that creates routes, i.e `createApmServerRoute` or `createObservabilityOnboardingServerRoute`, you can easily modify the eslint rule to handle your case.
|
|
For example, you register the route with `access` tags in your utility function:
|
|
```ts
|
|
createApmServerRoute({
|
|
endpoint: 'GET /your/route/path',
|
|
options: { tags: ['access:apm'] },
|
|
handler: async (resources): => {
|
|
// your handler logic
|
|
},
|
|
})
|
|
```
|
|
You can modify [the rule](https://github.com/elastic/kibana/blob/6a50066e00ae38a64c5365fd66b4dc32857ba1fc/packages/kbn-eslint-plugin-eslint/rules/no_deprecated_authz_config.js#L312-#L315) to handle your case by adding the following code:
|
|
```ts
|
|
callee.type === 'Identifier' && callee.name === 'createApmServerRoute'
|
|
```
|
|
|
|
## Questions?
|
|
If you have any questions or need help with API authorization, please reach out to the `@elastic/kibana-security` team.
|
|
|
|
|