Skip to content

Commit

Permalink
[RAC] [RBAC] adds rac client initialization to plugin setup / startup…
Browse files Browse the repository at this point in the history
… and adds some rac client functions to be implemented (#3)

* wip - ignore

* adds rac client initialization to plugin setup / startup and adds scaffolding for CRUD client functions

Co-authored-by: Yara Tercero <yara.tercero@elastic.co>
  • Loading branch information
dhurley14 and yctercero committed Apr 15, 2021
1 parent b97736e commit a501541
Show file tree
Hide file tree
Showing 6 changed files with 735 additions and 14 deletions.
115 changes: 115 additions & 0 deletions x-pack/plugins/rule_registry/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# RAC

The RAC plugin provides a common place to register rules with alerting. You can:

- Register types of rules
- Perform CRUD actions on rules
- Perform CRUD actions on alerts produced by rules

----

Table of Contents

- [Rule Registry](#rule-registry)
- [Role Based Access-Control](#rbac)

## Rule Registry
The rule registry plugin aims to make it easy for rule type producers to have their rules produce the data that they need to build rich experiences on top of a unified experience, without the risk of mapping conflicts.

A rule registry creates a template, an ILM policy, and an alias. The template mappings can be configured. It also injects a client scoped to these indices.
Expand Down Expand Up @@ -66,3 +82,102 @@ The following fields are available in the root rule registry:
- `kibana.rac.alert.severity.value`: the severity of the alert, as a numerical value, which allows sorting.

This list is not final - just a start. Field names might change or moved to a scoped registry. If we implement log and sequence based rule types the list of fields will grow. If a rule type needs additional fields, the recommendation would be to have the field in its own registry first (or in its producer’s registry), and if usage is more broadly adopted, it can be moved to the root registry.

## Role Based Access-Control

Rules registered through the rule registry produce `alerts` that are indexed into the `.alerts` index. Using the `producer` defined in the rule registry, these alerts inheret the `producer` property which is used in the auth to determine whether a user has access to these alerts and what operations they can perform on them.

Users will need to be granted access to these `alerts`. When registering a feature in Kibana you can specify multiple types of privileges which are granted to users when they're assigned certain roles. Assuming your feature generates `alerts`, you'll want to control which roles have all/read privileges for these alerts that are scoped to your feature. For example, the `security_solution` plugin allows users to create rules that generate `alerts`, so does `observability`. The `security_solution` plugin only wants to grant it's users access to `alerts` belonging to `security_solution`. However, a user may have access to numerous `alerts` like `['security_solution', 'observability']`.

You can control all of these abilities by assigning privileges to Alerts from within your own feature, for example:

```typescript
features.registerKibanaFeature({
id: 'my-application-id',
name: 'My Application',
app: [],
privileges: {
all: {
alerts: {
all: [
// grant `all` over our own types
'my-application-id.my-feature',
'my-application-id.my-restricted-alert-type',
// grant `all` over the built-in IndexThreshold
'.index-threshold',
// grant `all` over Uptime's TLS AlertType
'xpack.uptime.alerts.actionGroups.tls'
],
},
},
read: {
alerts: {
read: [
// grant `read` over our own type
'my-application-id.my-feature',
// grant `read` over the built-in IndexThreshold
'.index-threshold',
// grant `read` over Uptime's TLS AlertType
'xpack.uptime.alerts.actionGroups.tls'
],
},
},
},
});
```

In this example we can see the following:
- Our feature grants any user who's assigned the `all` role in our feature the `all` role in the Alerting framework over every alert of the `my-application-id.my-alert-type` type which is created _inside_ the feature. What that means is that this privilege will allow the user to execute any of the `all` operations (listed below) on these alerts as long as their `consumer` is `my-application-id`. Below that you'll notice we've done the same with the `read` role, which is grants the Alerting Framework's `read` role privileges over these very same alerts.
- In addition, our feature grants the same privileges over any alert of type `my-application-id.my-restricted-alert-type`, which is another hypothetical alertType registered by this feature. It's worth noting though that this type has been omitted from the `read` role. What this means is that only users with the `all` role will be able to interact with alerts of this type.
- Next, lets look at the `.index-threshold` and `xpack.uptime.alerts.actionGroups.tls` types. These have been specified in both `read` and `all`, which means that all the users in the feature will gain privileges over alerts of these types (as long as their `consumer` is `my-application-id`). The difference between these two and the previous two is that they are _produced_ by other features! `.index-threshold` is a built-in type, provided by the _Built-In Alerts_ feature, and `xpack.uptime.alerts.actionGroups.tls` is an AlertType provided by the _Uptime_ feature. Specifying these type here tells the Alerting Framework that as far as the `my-application-id` feature is concerned, the user is privileged to use them (with `all` and `read` applied), but that isn't enough. Using another feature's AlertType is only possible if both the producer of the AlertType, and the consumer of the AlertType, explicitly grant privileges to do so. In this case, the _Built-In Alerts_ & _Uptime_ features would have to explicitly add these privileges to a role and this role would have to be granted to this user.

It's important to note that any role can be granted a mix of `all` and `read` privileges accross multiple type, for example:

```typescript
features.registerKibanaFeature({
id: 'my-application-id',
name: 'My Application',
app: [],
privileges: {
all: {
app: ['my-application-id', 'kibana'],
savedObject: {
all: [],
read: [],
},
ui: [],
api: [],
},
read: {
app: ['lens', 'kibana'],
alerting: {
all: [
'my-application-id.my-alert-type'
],
read: [
'my-application-id.my-restricted-alert-type'
],
},
savedObject: {
all: [],
read: [],
},
ui: [],
api: [],
},
},
});
```

In the above example, you note that instead of denying users with the `read` role any access to the `my-application-id.my-restricted-alert-type` type, we've decided that these users _should_ be granted `read` privileges over the _resitricted_ AlertType.
As part of that same change, we also decided that not only should they be allowed to `read` the _restricted_ AlertType, but actually, despite having `read` privileges to the feature as a whole, we do actually want to allow them to create our basic 'my-application-id.my-alert-type' AlertType, as we consider it an extension of _reading_ data in our feature, rather than _writing_ it.

### `read` privileges vs. `all` privileges
When a user is granted the `read` role in for Alerts, they will be able to execute the following api calls:
- `get`
- `find`

When a user is granted the `all` role in the Alerting Framework, they will be able to execute all of the `read` privileged api calls, but in addition they'll be granted the following calls:
- `update`

Attempting to execute any operation the user isn't privileged to execute will result in an Authorization error thrown by the AlertsClient.
98 changes: 85 additions & 13 deletions x-pack/plugins/rule_registry/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,60 @@
* 2.0.
*/

import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server';
import {
Logger,
PluginInitializerContext,
Plugin,
CoreSetup,
CoreStart,
SharedGlobalConfig,
KibanaRequest,
IContextProvider,
} from 'src/core/server';
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
import { PluginSetupContract as AlertingPluginSetupContract } from '../../alerting/server';
import { SpacesPluginStart } from '../../spaces/server';
import { PluginStartContract as FeaturesPluginStart } from '../../features/server';

import { RuleRegistry } from './rule_registry';
import { defaultIlmPolicy } from './rule_registry/defaults/ilm_policy';
import { defaultFieldMap } from './rule_registry/defaults/field_map';
import { RacClientFactory } from './rac_client/rac_client_factory';
import { RuleRegistryConfig } from '.';
import { RacRequestHandlerContext } from './types';
export interface RacPluginsSetup {
security?: SecurityPluginSetup;
alerting: AlertingPluginSetupContract;
}
export interface RacPluginsStart {
security?: SecurityPluginStart;
spaces?: SpacesPluginStart;
features: FeaturesPluginStart;
}

export type RuleRegistryPluginSetupContract = RuleRegistry<typeof defaultFieldMap>;
export type RacPluginSetupContract = RuleRegistry<typeof defaultFieldMap>;

export class RuleRegistryPlugin implements Plugin<RacPluginSetupContract> {
private readonly globalConfig: SharedGlobalConfig;
private readonly config: RuleRegistryConfig;
private readonly racClientFactory: RacClientFactory;
private security?: SecurityPluginSetup;
private readonly logger: Logger;
private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];

export class RuleRegistryPlugin implements Plugin<RuleRegistryPluginSetupContract> {
constructor(private readonly initContext: PluginInitializerContext) {
this.initContext = initContext;
this.racClientFactory = new RacClientFactory();
this.globalConfig = this.initContext.config.legacy.get();
this.config = initContext.config.get<RuleRegistryConfig>();
this.logger = initContext.logger.get('root');
this.kibanaVersion = initContext.env.packageInfo.version;
}

public setup(
core: CoreSetup,
plugins: { alerting: AlertingPluginSetupContract }
): RuleRegistryPluginSetupContract {
const globalConfig = this.initContext.config.legacy.get();
const config = this.initContext.config.get<RuleRegistryConfig>();

const logger = this.initContext.logger.get();
public setup(core: CoreSetup, plugins: RacPluginsSetup): RacPluginSetupContract {
this.security = plugins.security;

// RULE REGISTRY
const rootRegistry = new RuleRegistry({
coreSetup: core,
ilmPolicy: defaultIlmPolicy,
Expand All @@ -37,13 +68,54 @@ export class RuleRegistryPlugin implements Plugin<RuleRegistryPluginSetupContrac
kibanaVersion: this.initContext.env.packageInfo.version,
logger: logger.get('root'),
alertingPluginSetupContract: plugins.alerting,
writeEnabled: config.writeEnabled,
writeEnabled: this.config.writeEnabled,
});

// ALERTS ROUTES
core.http.registerRouteHandlerContext<RacRequestHandlerContext, 'rac'>(
'rac',
this.createRouteHandlerContext()
);

return rootRegistry;
}

public start() {}
public start(core: CoreStart, plugins: RacPluginsStart) {
const { logger, security, racClientFactory } = this;

racClientFactory.initialize({
logger,
securityPluginSetup: security,
securityPluginStart: plugins.security,
getSpaceId(request: KibanaRequest) {
return plugins.spaces?.spacesService.getSpaceId(request);
},
async getSpace(request: KibanaRequest) {
return plugins.spaces?.spacesService.getActiveSpace(request);
},
features: plugins.features,
kibanaVersion: this.kibanaVersion,
});

const getRacClientWithRequest = (request: KibanaRequest) => {
return racClientFactory!.create(request);
};

return {
getRacClientWithRequest,
};
}

private createRouteHandlerContext = (): IContextProvider<RacRequestHandlerContext, 'rac'> => {
const { racClientFactory } = this;
return async function alertsRouteHandlerContext(context, request) {
return {
getRacClient: () => {
return racClientFactory!.create(request);
},
};
};
};

public stop() {}
}
Loading

0 comments on commit a501541

Please sign in to comment.