[Security Solutions] Removes POC transforms (#129673)

## Summary

Removes the `metrics_entities` plugin and POC. As a different direction will be taken and people can look back at the git history for it as they see fit if they need to refer to it. Once it's re-added it it will be through an RFC process and re-discussed.

Earlier PR's which added the POC:

https://github.com/elastic/kibana/pull/96446
https://github.com/elastic/kibana/pull/104559

### Checklist

- [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
This commit is contained in:
Frank Hassanabad 2022-04-11 13:41:09 -06:00 committed by GitHub
parent 268470a440
commit ce2f6171c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
189 changed files with 117 additions and 7545 deletions

View file

@ -1242,132 +1242,6 @@ module.exports = {
},
},
/**
* Metrics entities overrides. These rules below are maintained and owned by
* the people within the security-solution-platform team. Please see ping them
* or check with them if you are encountering issues, have suggestions, or would
* like to add, change, or remove any particular rule. Linters, Typescript, and rules
* evolve and change over time just like coding styles, so please do not hesitate to
* reach out.
*/
{
// front end and common typescript and javascript files only
files: [
'x-pack/plugins/metrics_entities/public/**/*.{js,mjs,ts,tsx}',
'x-pack/plugins/metrics_entities/common/**/*.{js,mjs,ts,tsx}',
],
rules: {
'import/no-nodejs-modules': 'error',
'no-restricted-imports': [
'error',
{
// prevents UI code from importing server side code and then webpack including it when doing builds
patterns: ['**/server/*'],
},
],
},
},
{
// typescript and javascript for front and back end
files: ['x-pack/plugins/metrics_entities/**/*.{js,mjs,ts,tsx}'],
plugins: ['eslint-plugin-node'],
env: {
jest: true,
},
rules: {
'accessor-pairs': 'error',
'array-callback-return': 'error',
'no-array-constructor': 'error',
complexity: 'error',
'consistent-return': 'error',
'func-style': ['error', 'expression'],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
},
],
'sort-imports': [
'error',
{
ignoreDeclarationSort: true,
},
],
'node/no-deprecated-api': 'error',
'no-bitwise': 'error',
'no-continue': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-duplicate-imports': 'error',
'no-empty-character-class': 'error',
'no-empty-pattern': 'error',
'no-ex-assign': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-extra-boolean-cast': 'error',
'no-extra-label': 'error',
'no-func-assign': 'error',
'no-implicit-globals': 'error',
'no-implied-eval': 'error',
'no-invalid-regexp': 'error',
'no-inner-declarations': 'error',
'no-lone-blocks': 'error',
'no-multi-assign': 'error',
'no-misleading-character-class': 'error',
'no-new-symbol': 'error',
'no-obj-calls': 'error',
'no-param-reassign': ['error', { props: true }],
'no-process-exit': 'error',
'no-prototype-builtins': 'error',
'no-return-await': 'error',
'no-self-compare': 'error',
'no-shadow-restricted-names': 'error',
'no-sparse-arrays': 'error',
'no-this-before-super': 'error',
// rely on typescript
'no-undef': 'off',
'no-unreachable': 'error',
'no-unsafe-finally': 'error',
'no-useless-call': 'error',
'no-useless-catch': 'error',
'no-useless-concat': 'error',
'no-useless-computed-key': 'error',
'no-useless-escape': 'error',
'no-useless-rename': 'error',
'no-useless-return': 'error',
'no-void': 'error',
'one-var-declaration-per-line': 'error',
'prefer-object-spread': 'error',
'prefer-promise-reject-errors': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'prefer-template': 'error',
'require-atomic-updates': 'error',
'symbol-description': 'error',
'vars-on-top': 'error',
'@typescript-eslint/explicit-member-accessibility': 'error',
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-useless-constructor': 'error',
'@typescript-eslint/unified-signatures': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'no-template-curly-in-string': 'error',
'sort-keys': 'error',
'prefer-destructuring': 'error',
'no-restricted-imports': [
'error',
{
// prevents code from importing files that contain the name "legacy" within their name. This is a mechanism
// to help deprecation and prevent accidental re-use/continued use of code we plan on removing. If you are
// finding yourself turning this off a lot for "new code" consider renaming the file and functions if it has valid uses.
patterns: ['*legacy*'],
},
],
},
},
/**
* Alerting Services overrides
*/

1
.github/CODEOWNERS vendored
View file

@ -394,7 +394,6 @@
/x-pack/test/plugin_functional/plugins/resolver_test/ @elastic/security-solution
/x-pack/test/plugin_functional/test_suites/resolver/ @elastic/security-solution
/x-pack/plugins/security_solution/ @elastic/security-solution
/x-pack/plugins/metrics_entities/ @elastic/security-solution
/x-pack/test/detection_engine_api_integration @elastic/security-solution
/x-pack/test/lists_api_integration @elastic/security-solution
/x-pack/test/api_integration/apis/security_solution @elastic/security-solution

View file

@ -524,12 +524,6 @@ using the CURL scripts in the scripts folder.
|Visualize geo data from Elasticsearch or 3rd party geo-services.
|{kib-repo}blob/{branch}/x-pack/plugins/metrics_entities/README.md[metricsEntities]
|This is the metrics and entities plugin where you add can add transforms for your project
and group those transforms into modules. You can also re-use existing transforms in your
newly created modules as well.
|{kib-repo}blob/{branch}/x-pack/plugins/ml/readme.md[ml]
|This plugin provides access to the machine learning features provided by
Elastic.

View file

@ -76,7 +76,6 @@ it('produces the right watch and ignore list', () => {
<absolute path>/x-pack/plugins/lists/server/scripts,
<absolute path>/x-pack/plugins/security_solution/scripts,
<absolute path>/x-pack/plugins/security_solution/server/lib/detection_engine/scripts,
<absolute path>/x-pack/plugins/metrics_entities/server/scripts,
<absolute path>/x-pack/plugins/uptime/e2e,
]
`);

View file

@ -66,7 +66,6 @@ export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) {
fromRoot('x-pack/plugins/lists/server/scripts'),
fromRoot('x-pack/plugins/security_solution/scripts'),
fromRoot('x-pack/plugins/security_solution/server/lib/detection_engine/scripts'),
fromRoot('x-pack/plugins/metrics_entities/server/scripts'),
fromRoot('x-pack/plugins/uptime/e2e'),
];

View file

@ -38,7 +38,6 @@
"xpack.logstash": ["plugins/logstash"],
"xpack.main": "legacy/plugins/xpack_main",
"xpack.maps": ["plugins/maps"],
"xpack.metricsEntities": "plugins/metrics_entities",
"xpack.ml": ["plugins/ml"],
"xpack.monitoring": ["plugins/monitoring"],
"xpack.osquery": ["plugins/osquery"],

View file

@ -1,322 +0,0 @@
# metrics_entities
This is the metrics and entities plugin where you add can add transforms for your project
and group those transforms into modules. You can also re-use existing transforms in your
newly created modules as well.
## Turn on experimental flags
During at least phase 1 of this development, please add these to your `kibana.dev.yml` file to turn on the feature:
```ts
xpack.metricsEntities.enabled: true
xpack.securitySolution.enableExperimental: ['metricsEntitiesEnabled']
```
## Quick start on using scripts to call the API
The scripts rely on CURL and jq:
- [CURL](https://curl.haxx.se)
- [jq](https://stedolan.github.io/jq/)
Install curl and jq
```sh
brew update
brew install curl
brew install jq
```
Open `$HOME/.zshrc` or `${HOME}.bashrc` depending on your SHELL output from `echo $SHELL`
and add these environment variables:
```sh
export ELASTICSEARCH_USERNAME=${user}
export ELASTICSEARCH_PASSWORD=${password}
export ELASTICSEARCH_URL=https://${ip}:9200
export KIBANA_URL=http://localhost:5601
```
source `$HOME/.zshrc` or `${HOME}.bashrc` to ensure variables are set:
```sh
source ~/.zshrc
```
Restart Kibana and ensure that you are using `--no-base-path` as changing the base path is a feature but will
get in the way of the CURL scripts written as is.
Go to the scripts folder `cd kibana/x-pack/plugins/metrics_entities/server/scripts` and can run some of the scripts
such as:
```sh
./post_transforms.sh ./post_examples/all.json
```
which will post transforms from the `all.json`
You can also delete them by running:
```sh
./delete_transforms.sh ./delete_examples/all.json
```
See the folder for other curl scripts that exercise parts of the REST API and feel free to add your own examples
in the folder as well.
## Quick start on how to add a transform
You will want to figure out how you want your transform from within Kibana roughly using
the UI and then copy the JSON. The JSON you will want to change and paste within a folder
which represents a module.
For example, for the `host_entities` and a `host_entities_mapping` we created a folder called host_entities
here:
```sh
sever/modules/host_entities
```
Then we add two files, a subset of the transform JSON and a mapping like so:
```sh
server/modules/host_entities/host_entities_mapping.json <--- this is the mappings
server/modules/host_entities/host_entities.json <--- This is a subset of the transform JSON
index.ts <--- Import/export your json here
```
The mappings can be normal mapping like so with `host_entities_mapping.json`:
```json
{
"mappings": {
"_meta": {
"index": "host_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"host": {
"properties": {
"name": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"host": {
"properties": {
"name": {
"type": "keyword"
},
"os": {
"properties": {
"name": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
}
}
}
}
}
}
```
One caveat is that you need to add this to the meta section to tell it what the name will be:
```json
"_meta": {
"index": "host_ent"
},
```
Keep the name short as there is only 65 characters for a transform job and we prepend extra information to the mapping such as:
* prefix
* name of estc
Although not required, a `"dynamic": "strict"` is strongly encouraged to prevent mapping guesses from elastic and it will be better for us
to spot errors quicker in the mappings such as type-o's if this is set to strict.
Next, for the transform, you should add a subset that doesn't have any additional settings or meta associated like so for `host_entities.json`:
```json
{
"id": "host_ent",
"description": "[host.name entities] grouped by @timestamp, host.name, os.name, and os.version, and aggregated on host.name",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"host.name": {
"terms": {
"field": "host.name"
}
},
"host.os.name": {
"terms": {
"field": "host.os.name",
"missing_bucket": true
}
},
"host.os.version": {
"terms": {
"field": "host.os.version",
"missing_bucket": true
}
}
},
"aggregations": {
"metrics.host.name.value_count": {
"value_count": {
"field": "host.name"
}
}
}
}
}
```
Look in the `server/modules` for other examples, but it should be that clear cut. The final part is to wire everything up in the code by touching a few files
to either add this to an existing module or create your own module. In `server/module/host_entities` we add an `index.ts` like so that does an import/export
of the JSON:
```sh
import hostEntities from './host_entities.json';
import hostEntitiesMapping from './host_entities_mapping.json';
export { hostEntities, hostEntitiesMapping };
```
Then in `modules/index.ts` we add a new module name if we are creating a new module to the `export enum ModuleNames {` like so:
```ts
// Import your host entities you just made
import { hostEntities, hostEntitiesMapping } from './host_entities';
/**
* These module names will map 1 to 1 to the REST interface.
*/
export enum ModuleNames {
hostSummaryMetrics = 'host_metrics',
hostSummaryEntities = 'host_entities', // <-- Add the entities/transform and give it a enum name and a module name
networkSummaryEntities = 'network_entities',
networkSummaryMetrics = 'network_metrics',
userSummaryEntities = 'user_entities',
userSummaryMetrics = 'user_metrics',
}
```
If you're not creating a new module but rather you are adding to an existing module, you can skip the above step. Next, you
just need to add your installable transform and installable mapping to the two data structures of `installableTransforms` and
`installableMappings` like so:
```ts
/**
* Add any new folders as modules with their names below and grouped with
* key values.
*/
export const installableTransforms: Record<ModuleNames, Transforms[]> = {
[ModuleNames.hostSummaryMetrics]: [hostMetrics],
[ModuleNames.hostSummaryEntities]: [hostEntities], // <-- Adds my new module name and transform to a new array.
[ModuleNames.networkSummaryEntities]: [
destinationIpEntities, // <-- If instead I am adding to an existing module, I just add it to the array like these show
sourceIpEntities,
destinationCountryIsoCodeEntities,
sourceCountryIsoCodeEntities,
],
[ModuleNames.networkSummaryMetrics]: [ipMetrics],
[ModuleNames.userSummaryEntities]: [userEntities],
[ModuleNames.userSummaryMetrics]: [userMetrics],
};
/**
* For all the mapping types, add each with their names below and grouped with
* key values.
*/
export const installableMappings: Record<ModuleNames, Mappings[]> = {
[ModuleNames.hostSummaryMetrics]: [hostMetricsMapping],
[ModuleNames.hostSummaryEntities]: [hostEntitiesMapping], // <-- Adds my new module name and mapping to a new array.
[ModuleNames.networkSummaryEntities]: [ // <-- If instead I am adding to an existing module, I just add it to the array like these show
sourceIpEntitiesMapping,
destinationIpEntitiesMapping,
destinationCountryIsoCodeEntitiesMapping,
sourceCountryIsoCodeEntitiesMapping,
],
[ModuleNames.networkSummaryMetrics]: [ipMetricsMapping],
[ModuleNames.userSummaryEntities]: [userEntitiesMapping],
[ModuleNames.userSummaryMetrics]: [userMetricsMapping],
};
```
And after that, you should check out if there are any existing e2e tests or unit tests to update here to ensure that your mapping and transform will
pass ci. Create a pull request and your mapping and transform are completed.
To call into the code to activate your module and create your transforms and mappings would be the following where you substitute your
${KIBANA_URL} with your kibana URL and the ${SPACE_URL} with any space id you have. If you're using the default space then you would use
an empty string:
```json
POST ${KIBANA_URL}${SPACE_URL}/api/metrics_entities/transforms
{
"prefix": "all",
"modules": [
"host_entities",
],
"indices": [
"auditbeat-*",
"endgame-*",
"filebeat-*",
"logs-*",
"packetbeat-*",
"winlogbeat-*",
"-*elastic-cloud-logs-*"
],
"auto_start": true,
"settings": {
"max_page_search_size": 5000
},
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"format": "strict_date_optional_time"
}
}
}
}
```
Very similar to the regular transform REST API, with the caveats that you define which modules you want to install, the prefix name you want to use, and
if you want to `auto_start` it or not. The rest such as `settings`, `query` will be the same as the transforms API. They will also push those same setting into
each of your transforms within your module(s) as the same setting for each individual ones.
## TODO List
During the phase 1, phase 2, phase N, this TODO will exist as a reminder and notes for what still needs to be developed. These are not in a priority order, but
are notes during the phased approach. As we approach production and the feature flags are removed these TODO's should be removed in favor of Kibana issues or regular
left over TODO's in the code base.
- Add these properties to the route which are:
- disable_transforms/exclude flag to exclude 1 or more transforms within a module
- pipeline flag
- Change the REST routes on post to change the indexes for whichever indexes you want
- Unit tests to ensure the data of the mapping.json includes the correct fields such as _meta, at least one alias, a mapping section, etc...
- Add text/keyword and other things to the mappings (not just keyword maybe?) ... At least review the mappings one more time
- Add a sort of @timestamp to the output destination indexes?
- Add the REST Kibana security based tags if needed and push those to any plugins using this plugin. Something like: tags: ['access:metricsEntities-read'] and ['access:metricsEntities-all'],
- Add schema validation choosing some schema library (io-ts or Kibana Schema or ... )
- Add unit tests
- Add e2e tests
- Any UI code should not end up here. There is none right now, but all UI code should be within a kbn package or security_solutions

View file

@ -1,21 +0,0 @@
/*
* 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.
*/
/**
* Base route
*/
export const METRICS_ENTITIES_URL = '/api/metrics_entities';
/**
* Transforms route
*/
export const METRICS_ENTITIES_TRANSFORMS = `${METRICS_ENTITIES_URL}/transforms`;
/**
* Global prefix for all the transform jobs
*/
export const ELASTIC_NAME = 'estc';

View file

@ -1,13 +0,0 @@
/*
* 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.
*/
// Careful of exporting anything from this file as any file(s) you export here will cause your functions to be exposed as public.
// If you're using functions/types/etc... internally or within integration tests it's best to import directly from their paths
// than expose the functions/types/etc... here. You should _only_ expose functions/types/etc... that need to be shared with other plugins here.
// When you do have to add things here you might want to consider creating a package such to share with other plugins instead as packages
// are easier to break down.
// See: https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro#public-plugin-api

View file

@ -1,14 +0,0 @@
{
"id": "metricsEntities",
"owner": {
"name": "Security solution",
"githubTeam": "security-solution"
},
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "metricsEntities"],
"server": true,
"ui": false,
"requiredPlugins": ["data", "dataEnhanced"],
"optionalPlugins": []
}

View file

@ -1,17 +0,0 @@
/*
* 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 class ErrorWithStatusCode extends Error {
private readonly statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
}
public getStatusCode = (): number => this.statusCode;
}

View file

@ -1,28 +0,0 @@
/*
* 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 { schema } from '@kbn/config-schema';
import { PluginInitializerContext } from '../../../../src/core/server';
import { MetricsEntitiesPlugin } from './plugin';
// This exports static code and TypeScript types,
// as well as, Kibana Platform `plugin()` initializer.
export const plugin = (initializerContext: PluginInitializerContext): MetricsEntitiesPlugin => {
return new MetricsEntitiesPlugin(initializerContext);
};
export type { MetricsEntitiesPluginSetup, MetricsEntitiesPluginStart } from './types';
export const config = {
schema: schema.object({
// This plugin is experimental and should be disabled by default.
enabled: schema.boolean({ defaultValue: false }),
}),
};

View file

@ -1,4 +0,0 @@
# Modules
This is where all the module types exist so you can load different bundled modules
with a REST endpoint.

View file

@ -1,38 +0,0 @@
{
"id": "host_ent",
"description": "[host.name entities] grouped by @timestamp, host.name, os.name, and os.version, and aggregated on host.name",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"host.name": {
"terms": {
"field": "host.name"
}
},
"host.os.name": {
"terms": {
"field": "host.os.name",
"missing_bucket": true
}
},
"host.os.version": {
"terms": {
"field": "host.os.version",
"missing_bucket": true
}
}
},
"aggregations": {
"metrics.host.name.value_count": {
"value_count": {
"field": "host.name"
}
}
}
}
}

View file

@ -1,45 +0,0 @@
{
"mappings": {
"_meta": {
"index": "host_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"host": {
"properties": {
"name": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"host": {
"properties": {
"name": {
"type": "keyword"
},
"os": {
"properties": {
"name": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
}
}
}
}
}
}

View file

@ -1,10 +0,0 @@
/*
* 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 hostEntities from './host_entities.json';
import hostEntitiesMapping from './host_entities_mapping.json';
export { hostEntities, hostEntitiesMapping };

View file

@ -1,21 +0,0 @@
{
"id": "host_met",
"description": "[host.name metrics] grouped by @timestamp and aggregated on host.name",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
}
},
"aggregations": {
"metrics.host.name.cardinality": {
"cardinality": {
"field": "host.name"
}
}
}
}
}

View file

@ -1,83 +0,0 @@
{
"mappings": {
"_meta": {
"index": "host_met"
},
"properties": {
"metrics": {
"properties": {
"source": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"network": {
"properties": {
"community_id": {
"properties": {
"cardinality": {
"type": "long"
}
}
}
}
},
"host": {
"properties": {
"name": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
}
}
}
}
},
"@timestamp": {
"type": "date"
}
}
}
}

View file

@ -1,11 +0,0 @@
/*
* 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 hostMetrics from './host_metrics.json';
import hostMetricsMapping from './host_metrics_mapping.json';
export { hostMetrics, hostMetricsMapping };

View file

@ -1,71 +0,0 @@
/*
* 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 { hostMetrics, hostMetricsMapping } from './host_metrics';
import { userMetrics, userMetricsMapping } from './user_metrics';
import { ipMetrics, ipMetricsMapping } from './network_metrics';
import { hostEntities, hostEntitiesMapping } from './host_entities';
import {
destinationCountryIsoCodeEntities,
destinationCountryIsoCodeEntitiesMapping,
destinationIpEntities,
destinationIpEntitiesMapping,
sourceCountryIsoCodeEntities,
sourceCountryIsoCodeEntitiesMapping,
sourceIpEntities,
sourceIpEntitiesMapping,
} from './network_entities';
import { Mappings, Transforms } from './types';
import { userEntities, userEntitiesMapping } from './user_entities';
/**
* These module names will map 1 to 1 to the REST interface.
*/
export enum ModuleNames {
hostSummaryMetrics = 'host_metrics',
hostSummaryEntities = 'host_entities',
networkSummaryEntities = 'network_entities',
networkSummaryMetrics = 'network_metrics',
userSummaryEntities = 'user_entities',
userSummaryMetrics = 'user_metrics',
}
/**
* Add any new folders as modules with their names below and grouped with
* key values.
*/
export const installableTransforms: Record<ModuleNames, Transforms[]> = {
[ModuleNames.hostSummaryMetrics]: [hostMetrics],
[ModuleNames.hostSummaryEntities]: [hostEntities],
[ModuleNames.networkSummaryEntities]: [
destinationIpEntities,
sourceIpEntities,
destinationCountryIsoCodeEntities,
sourceCountryIsoCodeEntities,
],
[ModuleNames.networkSummaryMetrics]: [ipMetrics],
[ModuleNames.userSummaryEntities]: [userEntities],
[ModuleNames.userSummaryMetrics]: [userMetrics],
};
/**
* For all the mapping types, add each with their names below and grouped with
* key values.
*/
export const installableMappings: Record<ModuleNames, Mappings[]> = {
[ModuleNames.hostSummaryMetrics]: [hostMetricsMapping],
[ModuleNames.hostSummaryEntities]: [hostEntitiesMapping],
[ModuleNames.networkSummaryEntities]: [
sourceIpEntitiesMapping,
destinationIpEntitiesMapping,
destinationCountryIsoCodeEntitiesMapping,
sourceCountryIsoCodeEntitiesMapping,
],
[ModuleNames.networkSummaryMetrics]: [ipMetricsMapping],
[ModuleNames.userSummaryEntities]: [userEntitiesMapping],
[ModuleNames.userSummaryMetrics]: [userMetricsMapping],
};

View file

@ -1,51 +0,0 @@
{
"id": "dest_iso_ent",
"description": "[destination.geo.country_iso_code entities] grouped by @timestamp and aggregated on source.bytes, destination.bytes, network.community_id, destination.ip, and source.ip",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"destination.geo.country_iso_code": {
"terms": {
"field": "destination.geo.country_iso_code"
}
}
},
"aggregations": {
"metrics.destination.geo.country_iso_code.value_count": {
"value_count": {
"field": "destination.geo.country_iso_code"
}
},
"metrics.source.bytes.sum": {
"sum": {
"field": "source.bytes"
}
},
"metrics.destination.bytes.sum": {
"sum": {
"field": "destination.bytes"
}
},
"metrics.network.community_id.cardinality": {
"cardinality": {
"field": "network.community_id"
}
},
"metrics.source.ip.cardinality": {
"cardinality": {
"field": "source.ip"
}
},
"metrics.destination.ip.cardinality": {
"cardinality": {
"field": "destination.ip"
}
}
}
}
}

View file

@ -1,120 +0,0 @@
{
"mappings": {
"_meta": {
"index": "dest_iso_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"source": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
},
"geo": {
"properties": {
"country_iso_code": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
},
"geo": {
"properties": {
"country_iso_code": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"network": {
"properties": {
"community_id": {
"properties": {
"cardinality": {
"type": "long"
}
}
}
}
}
}
},
"source": {
"properties": {
"ip": {
"type": "ip"
},
"geo": {
"properties": {
"country_iso_code": {
"type": "keyword"
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"type": "ip"
},
"geo": {
"properties": {
"country_iso_code": {
"type": "keyword"
}
}
}
}
}
}
}
}

View file

@ -1,46 +0,0 @@
{
"id": "dest_ip_ent",
"description": "[destination.ip entities] grouped by @timestamp and aggregated on destination.ip, source.bytes, destination.bytes, network.community_id, and source.ip",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"destination.ip": {
"terms": {
"field": "destination.ip"
}
}
},
"aggregations": {
"metrics.destination.ip.value_count": {
"value_count": {
"field": "destination.ip"
}
},
"metrics.source.bytes.sum": {
"sum": {
"field": "source.bytes"
}
},
"metrics.destination.bytes.sum": {
"sum": {
"field": "destination.bytes"
}
},
"metrics.network.community_id.cardinality": {
"cardinality": {
"field": "network.community_id"
}
},
"metrics.source.ip.cardinality": {
"cardinality": {
"field": "source.ip"
}
}
}
}
}

View file

@ -1,84 +0,0 @@
{
"mappings": {
"_meta": {
"index": "dest_ip_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"source": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"network": {
"properties": {
"community_id": {
"properties": {
"cardinality": {
"type": "long"
}
}
}
}
}
}
},
"source": {
"properties": {
"ip": {
"type": "ip"
}
}
},
"destination": {
"properties": {
"ip": {
"type": "ip"
}
}
}
}
}
}

View file

@ -1,26 +0,0 @@
/*
* 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 sourceIpEntities from './source_ip_entities.json';
import destinationIpEntities from './destination_ip_entities.json';
import sourceIpEntitiesMapping from './source_ip_entities_mapping.json';
import destinationIpEntitiesMapping from './destination_ip_entities_mapping.json';
import destinationCountryIsoCodeEntities from './destination_country_iso_code_entities.json';
import destinationCountryIsoCodeEntitiesMapping from './destination_country_iso_code_entities_mapping.json';
import sourceCountryIsoCodeEntities from './source_country_iso_code_entities.json';
import sourceCountryIsoCodeEntitiesMapping from './source_country_iso_code_entities_mapping.json';
export {
sourceIpEntities,
destinationIpEntities,
sourceCountryIsoCodeEntities,
sourceCountryIsoCodeEntitiesMapping,
destinationCountryIsoCodeEntities,
destinationCountryIsoCodeEntitiesMapping,
sourceIpEntitiesMapping,
destinationIpEntitiesMapping,
};

View file

@ -1,51 +0,0 @@
{
"id": "src_iso_ent",
"description": "[source.geo.country_iso_code entities] grouped by @timestamp and aggregated on source.geo.country_iso_code, source.bytes, destination.bytes, network.community_id, source.ip, and destination.ip",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"source.geo.country_iso_code": {
"terms": {
"field": "source.geo.country_iso_code"
}
}
},
"aggregations": {
"metrics.source.geo.country_iso_code.value_count": {
"value_count": {
"field": "source.geo.country_iso_code"
}
},
"metrics.source.bytes.sum": {
"sum": {
"field": "source.bytes"
}
},
"metrics.destination.bytes.sum": {
"sum": {
"field": "destination.bytes"
}
},
"metrics.network.community_id.cardinality": {
"cardinality": {
"field": "network.community_id"
}
},
"metrics.source.ip.cardinality": {
"cardinality": {
"field": "source.ip"
}
},
"metrics.destination.ip.cardinality": {
"cardinality": {
"field": "destination.ip"
}
}
}
}
}

View file

@ -1,120 +0,0 @@
{
"mappings": {
"_meta": {
"index": "src_iso_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"source": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
},
"geo": {
"properties": {
"country_iso_code": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
},
"geo": {
"properties": {
"country_iso_code": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"network": {
"properties": {
"community_id": {
"properties": {
"cardinality": {
"type": "long"
}
}
}
}
}
}
},
"source": {
"properties": {
"ip": {
"type": "ip"
},
"geo": {
"properties": {
"country_iso_code": {
"type": "keyword"
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"type": "ip"
},
"geo": {
"properties": {
"country_iso_code": {
"type": "keyword"
}
}
}
}
}
}
}
}

View file

@ -1,46 +0,0 @@
{
"id": "src_ip_ent",
"description": "[source.ip entities] grouped by @timestamp and aggregated on destination.ip, source.bytes, destination.bytes, network.community_id, and destination.ip",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"source.ip": {
"terms": {
"field": "source.ip"
}
}
},
"aggregations": {
"metrics.source.ip.value_count": {
"value_count": {
"field": "source.ip"
}
},
"metrics.source.bytes.sum": {
"sum": {
"field": "source.bytes"
}
},
"metrics.destination.bytes.sum": {
"sum": {
"field": "destination.bytes"
}
},
"metrics.network.community_id.cardinality": {
"cardinality": {
"field": "network.community_id"
}
},
"metrics.destination.ip.cardinality": {
"cardinality": {
"field": "destination.ip"
}
}
}
}
}

View file

@ -1,84 +0,0 @@
{
"mappings": {
"_meta": {
"index": "src_ip_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"source": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"network": {
"properties": {
"community_id": {
"properties": {
"cardinality": {
"type": "long"
}
}
}
}
}
}
},
"source": {
"properties": {
"ip": {
"type": "ip"
}
}
},
"destination": {
"properties": {
"ip": {
"type": "ip"
}
}
}
}
}
}

View file

@ -1,11 +0,0 @@
/*
* 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 ipMetrics from './ip_metrics.json';
import ipMetricsMapping from './ip_metrics_mapping.json';
export { ipMetrics, ipMetricsMapping };

View file

@ -1,116 +0,0 @@
{
"id": "ip_met",
"description": "[source.ip metrics] grouped by @timestamp, source.ip, destination.ip and aggregated on tls.version, suricata.eve.tls.version, zeek.ssl.version, dns.question.name, and zeek.dns.query",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
}
},
"aggregations": {
"metrics.source.ip.cardinality": {
"cardinality": {
"field": "source.ip"
}
},
"metrics.destination.ip.cardinality": {
"cardinality": {
"field": "destination.ip"
}
},
"metrics.network": {
"filter": {
"bool": {
"should": [
{
"exists": {
"field": "source.ip"
}
},
{
"exists": {
"field": "destination.ip"
}
}
],
"minimum_should_match": 1
}
},
"aggs": {
"events.value_count": {
"value_count": {
"field": "@timestamp"
}
},
"tls": {
"filter": {
"bool": {
"should": [
{
"exists": {
"field": "tls.version"
}
},
{
"exists": {
"field": "suricata.eve.tls.version"
}
},
{
"exists": {
"field": "zeek.ssl.version"
}
}
],
"minimum_should_match": 1
}
},
"aggs": {
"version.value_count": {
"value_count": {
"field": "@timestamp"
}
}
}
}
}
},
"metrics.dns": {
"filter": {
"bool": {
"should": [
{
"exists": {
"field": "dns.question.name"
}
},
{
"term": {
"suricata.eve.dns.type": {
"value": "query"
}
}
},
{
"exists": {
"field": "zeek.dns.query"
}
}
],
"minimum_should_match": 1
}
},
"aggs": {
"queries.value_count": {
"value_count": {
"field": "@timestamp"
}
}
}
}
}
}
}

View file

@ -1,92 +0,0 @@
{
"mappings": {
"_meta": {
"index": "ip_met"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"source": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"destination": {
"properties": {
"ip": {
"properties": {
"value_count": {
"type": "long"
},
"cardinality": {
"type": "long"
}
}
},
"bytes": {
"properties": {
"sum": {
"type": "long"
}
}
}
}
},
"network": {
"properties": {
"events": {
"properties": {
"value_count": {
"type": "long"
}
}
},
"tls": {
"properties": {
"version": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
},
"dns": {
"properties": {
"queries": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
}
}
}
}

View file

@ -1,38 +0,0 @@
/*
* 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.
*/
/**
* Loose type for the mappings
*/
export interface Mappings {
[key: string]: unknown;
mappings: {
[key: string]: unknown;
_meta: {
index: string;
};
};
}
/**
* Loose type for the transforms. id is marked optional so we can delete it before
* pushing it through elastic client.
* TODO: Can we use stricter pre-defined typings for the transforms here or is this ours because we define it slightly different?
*/
export interface Transforms {
[key: string]: unknown;
id: string;
dest?: Partial<{
index: string;
pipeline: string;
}>;
source?: Partial<{}>;
settings?: Partial<{
max_page_search_size: number;
docs_per_second: number | null;
}>;
}

View file

@ -1,10 +0,0 @@
/*
* 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 userEntities from './user_entities.json';
import userEntitiesMapping from './user_entities_mapping.json';
export { userEntities, userEntitiesMapping };

View file

@ -1,51 +0,0 @@
{
"id": "user_ent",
"description": "[user.name entities] grouped by @timestamp and aggregated on user.name, and event.categories of success, failure, and unknown",
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"user.name": {
"terms": {
"field": "user.name"
}
}
},
"aggregations": {
"metrics.event.authentication": {
"filter": {
"term": {
"event.category": "authentication"
}
},
"aggs": {
"success.value_count": {
"filter": {
"term": {
"event.outcome": "success"
}
}
},
"failure.value_count": {
"filter": {
"term": {
"event.outcome": "failure"
}
}
},
"unknown.value_count": {
"filter": {
"term": {
"event.outcome": "unknown"
}
}
}
}
}
}
}
}

View file

@ -1,53 +0,0 @@
{
"mappings": {
"_meta": {
"index": "user_ent"
},
"dynamic": "strict",
"properties": {
"@timestamp": {
"type": "date"
},
"metrics": {
"properties": {
"event": {
"properties": {
"authentication": {
"properties": {
"failure": {
"properties": {
"value_count": {
"type": "long"
}
}
},
"success": {
"properties": {
"value_count": {
"type": "long"
}
}
},
"unknown": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
}
}
},
"user": {
"properties": {
"name": {
"type": "keyword"
}
}
}
}
}
}

View file

@ -1,11 +0,0 @@
/*
* 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 userMetrics from './user_metrics.json';
import userMetricsMapping from './user_metrics_mapping.json';
export { userMetrics, userMetricsMapping };

View file

@ -1,56 +0,0 @@
{
"id": "user_met",
"description": "[event.category authentication metrics] grouped by @timestamp and aggregated on success, failure, and unknown",
"source": {
"query": {
"bool": {
"filter": [
{
"bool": {
"filter": [
{
"term": {
"event.category": "authentication"
}
}
]
}
}
]
}
}
},
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
}
},
"aggregations": {
"metrics.event.authentication.success.value_count": {
"filter": {
"term": {
"event.outcome": "success"
}
}
},
"metrics.event.authentication.failure.value_count": {
"filter": {
"term": {
"event.outcome": "failure"
}
}
},
"metrics.event.authentication.unknown.value_count": {
"filter": {
"term": {
"event.outcome": "unknown"
}
}
}
}
}
}

View file

@ -1,46 +0,0 @@
{
"mappings": {
"_meta": {
"index": "user_met"
},
"dynamic": "strict",
"properties": {
"metrics": {
"properties": {
"event": {
"properties": {
"authentication": {
"properties": {
"failure": {
"properties": {
"value_count": {
"type": "long"
}
}
},
"success": {
"properties": {
"value_count": {
"type": "long"
}
}
},
"unknown": {
"properties": {
"value_count": {
"type": "long"
}
}
}
}
}
}
}
}
},
"@timestamp": {
"type": "date"
}
}
}
}

View file

@ -1,90 +0,0 @@
/*
* 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 {
CoreSetup,
CoreStart,
Logger,
Plugin,
PluginInitializerContext,
} from '../../../../src/core/server';
import {
ContextProvider,
ContextProviderReturn,
MetricsEntitiesPluginSetup,
MetricsEntitiesPluginStart,
MetricsEntitiesRequestHandlerContext,
} from './types';
import { getTransforms, postTransforms } from './routes';
import { MetricsEntitiesClient } from './services/metrics_entities_client';
import { deleteTransforms } from './routes/delete_transforms';
export class MetricsEntitiesPlugin
implements Plugin<MetricsEntitiesPluginSetup, MetricsEntitiesPluginStart>
{
private readonly logger: Logger;
private kibanaVersion: string;
constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
this.kibanaVersion = initializerContext.env.packageInfo.version;
}
public setup(core: CoreSetup): MetricsEntitiesPluginSetup {
const router = core.http.createRouter();
core.http.registerRouteHandlerContext<MetricsEntitiesRequestHandlerContext, 'metricsEntities'>(
'metricsEntities',
this.createRouteHandlerContext()
);
// Register server side APIs
// TODO: Add all of these into a separate file and call that file called init_routes.ts
getTransforms(router);
postTransforms(router);
deleteTransforms(router);
return {
getMetricsEntitiesClient: (esClient): MetricsEntitiesClient =>
new MetricsEntitiesClient({
esClient,
kibanaVersion: this.kibanaVersion,
logger: this.logger,
}),
};
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public start(core: CoreStart): void {
this.logger.debug('Starting plugin');
}
public stop(): void {
this.logger.debug('Stopping plugin');
}
private createRouteHandlerContext = (): ContextProvider => {
return async (context): ContextProviderReturn => {
const {
core: {
elasticsearch: {
client: { asCurrentUser: esClient },
},
},
} = context;
return {
getMetricsEntitiesClient: (): MetricsEntitiesClient =>
new MetricsEntitiesClient({
esClient,
kibanaVersion: this.kibanaVersion,
logger: this.logger,
}),
};
};
};
}

View file

@ -1,54 +0,0 @@
/*
* 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 { schema } from '@kbn/config-schema';
import { IRouter } from '../../../../../src/core/server';
import { METRICS_ENTITIES_TRANSFORMS } from '../../common/constants';
import { ModuleNames } from '../modules';
import { getMetricsEntitiesClient } from './utils/get_metrics_entities_client';
/**
* Deletes transforms.
* NOTE: We use a POST rather than a DELETE on purpose here to ensure that there
* are not problems with the body being sent.
* @param router The router to delete the collection of transforms
*/
export const deleteTransforms = (router: IRouter): void => {
router.post(
{
path: `${METRICS_ENTITIES_TRANSFORMS}/_delete`,
validate: {
// TODO: Add the validation instead of allowing handler to have access to raw non-validated in runtime
body: schema.object({}, { unknowns: 'allow' }),
query: schema.object({}, { unknowns: 'allow' }),
},
},
async (context, request, response) => {
// TODO: Type this through validation above and remove the weird casting of: "as { modules: ModuleNames };"
// TODO: Validate for runtime that the module exists or not and throw before pushing the module name lower
// TODO: Change modules to be part of the body and become an array of values
// TODO: Wrap this in a try catch block and report errors
const {
modules,
prefix = '',
suffix = '',
} = request.body as {
modules: ModuleNames[];
prefix: string;
suffix: string;
};
const metrics = getMetricsEntitiesClient(context);
await metrics.deleteTransforms({ modules, prefix, suffix });
return response.custom({
statusCode: 204,
});
}
);
};

View file

@ -1,36 +0,0 @@
/*
* 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 { IRouter } from '../../../../../src/core/server';
import { METRICS_ENTITIES_TRANSFORMS } from '../../common/constants';
import { getMetricsEntitiesClient } from './utils/get_metrics_entities_client';
/**
* Returns all transforms from all modules
* TODO: Add support for specific modules and prefix
* @param router The router to get the collection of transforms
*/
export const getTransforms = (router: IRouter): void => {
router.get(
{
path: METRICS_ENTITIES_TRANSFORMS,
// TODO: Add the validation instead of false
// TODO: Add the prefix and module support
validate: false,
},
async (context, _, response) => {
const metrics = getMetricsEntitiesClient(context);
const summaries = await metrics.getTransforms();
return response.ok({
body: {
summaries,
},
});
}
);
};

View file

@ -1,10 +0,0 @@
/*
* 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 * from './delete_transforms';
export * from './get_transforms';
export * from './post_transforms';

View file

@ -1,96 +0,0 @@
/*
* 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 { schema } from '@kbn/config-schema';
import { IRouter } from '../../../../../src/core/server';
import { METRICS_ENTITIES_TRANSFORMS } from '../../common/constants';
import { ModuleNames } from '../modules';
import { getMetricsEntitiesClient } from './utils/get_metrics_entities_client';
/**
* Creates transforms.
* @param router The router to get the collection of transforms
*/
export const postTransforms = (router: IRouter): void => {
router.post(
{
path: METRICS_ENTITIES_TRANSFORMS,
validate: {
// TODO: Add the validation instead of allowing handler to have access to raw non-validated in runtime
body: schema.object({}, { unknowns: 'allow' }),
query: schema.object({}, { unknowns: 'allow' }),
},
},
async (context, request, response) => {
// TODO: Type this through validation above and remove the weird casting of: "as { modules: ModuleNames };"
// TODO: Validate for runtime that the module exists or not and throw before pushing the module name lower
// TODO: Change modules to be part of the body and become an array of values
// TODO: Wrap this in a try catch block and report errors
const {
modules,
auto_start: autoStart = false,
settings: {
max_page_search_size: maxPageSearchSize = 500,
docs_per_second: docsPerSecond = undefined,
} = {
docsPerSecond: undefined,
maxPageSearchSize: 500,
},
frequency = '1m',
indices,
query = { match_all: {} },
prefix = '',
suffix = '',
sync = {
time: {
delay: '60s',
field: '@timestamp',
},
},
} = request.body as {
modules: ModuleNames[];
auto_start: boolean;
indices: string[];
// We can blow up at 65 character+ for transform id. We need to validate the prefix + transform jobs and return an error
prefix: string;
query: object;
suffix: string;
frequency: string;
settings: {
max_page_search_size: number;
docs_per_second: number;
};
sync: {
time: {
delay: string;
field: string;
};
};
};
const metrics = getMetricsEntitiesClient(context);
await metrics.postTransforms({
autoStart,
docsPerSecond,
frequency,
indices,
maxPageSearchSize,
modules,
prefix,
query,
suffix,
sync,
});
return response.custom({
body: { acknowledged: true },
statusCode: 201,
});
}
);
};

View file

@ -1,21 +0,0 @@
/*
* 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 { ErrorWithStatusCode } from '../../error_with_status_code';
import { MetricsEntitiesClient } from '../../services/metrics_entities_client';
import type { MetricsEntitiesRequestHandlerContext } from '../../types';
export const getMetricsEntitiesClient = (
context: MetricsEntitiesRequestHandlerContext
): MetricsEntitiesClient => {
const metricsEntities = context.metricsEntities?.getMetricsEntitiesClient();
if (metricsEntities == null) {
throw new ErrorWithStatusCode('Metrics Entities is not found as a plugin', 404);
} else {
return metricsEntities;
}
};

View file

@ -1,8 +0,0 @@
/*
* 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 * from './get_metrics_entities_client';

View file

@ -1,32 +0,0 @@
#!/bin/sh
#
# 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.
#
# Add this to the start of any scripts to detect if env variables are set
set -e
if [ -z "${ELASTICSEARCH_USERNAME}" ]; then
echo "Set ELASTICSEARCH_USERNAME in your environment"
exit 1
fi
if [ -z "${ELASTICSEARCH_PASSWORD}" ]; then
echo "Set ELASTICSEARCH_PASSWORD in your environment"
exit 1
fi
if [ -z "${ELASTICSEARCH_URL}" ]; then
echo "Set ELASTICSEARCH_URL in your environment"
exit 1
fi
if [ -z "${KIBANA_URL}" ]; then
echo "Set KIBANA_URL in your environment"
exit 1
fi

View file

@ -1,11 +0,0 @@
{
"prefix": "all",
"modules": [
"host_metrics",
"host_entities",
"network_metrics",
"network_entities",
"user_entities",
"user_metrics"
]
}

View file

@ -1,11 +0,0 @@
{
"prefix": "auditbeat",
"modules": [
"host_metrics",
"host_entities",
"network_metrics",
"network_entities",
"user_entities",
"user_metrics"
]
}

View file

@ -1,3 +0,0 @@
{
"modules": ["network_entities"]
}

View file

@ -1,3 +0,0 @@
{
"modules": ["user_entities"]
}

View file

@ -1,3 +0,0 @@
{
"modules": ["host_metrics", "host_entities"]
}

View file

@ -1,23 +0,0 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
# Uses a default if no argument is specified
FILE=${1:-./post_examples/one_module.json}
# Example: ./delete_transforms.sh ./delete_examples/one_module.json
curl -s -k \
-H 'Content-Type: application/json' \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X POST ${KIBANA_URL}${SPACE_URL}/api/metrics_entities/transforms/_delete \
-d @${FILE} \
| jq .

View file

@ -1,16 +0,0 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
# Example: ./get_transforms.sh
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/metrics_entities/transforms | jq .

View file

@ -1,17 +0,0 @@
#!/bin/sh
#
# 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.
#
# TODO Make this work
set -e
./check_env_variables.sh
# remove all templates
# add all templates again and start them

View file

@ -1,32 +0,0 @@
{
"prefix": "all",
"modules": [
"host_metrics",
"host_entities",
"network_metrics",
"network_entities",
"user_entities",
"user_metrics"
],
"indices": [
"auditbeat-*",
"endgame-*",
"filebeat-*",
"logs-*",
"packetbeat-*",
"winlogbeat-*",
"-*elastic-cloud-logs-*"
],
"auto_start": true,
"settings": {
"max_page_search_size": 5000
},
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"format": "strict_date_optional_time"
}
}
}
}

View file

@ -1,23 +0,0 @@
{
"modules": [
"host_metrics",
"host_entities",
"network_metrics",
"network_entities",
"user_entities",
"user_metrics"
],
"indices": ["auditbeat-*"],
"auto_start": true,
"settings": {
"max_page_search_size": 5000
},
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"format": "strict_date_optional_time"
}
}
}
}

View file

@ -1,4 +0,0 @@
{
"modules": ["network_entities"],
"indices": ["auditbeat-*"]
}

View file

@ -1,24 +0,0 @@
{
"modules": ["network_metrics"],
"indices": [
"auditbeat-*",
"endgame-*",
"filebeat-*",
"logs-*",
"packetbeat-*",
"winlogbeat-*",
"-*elastic-cloud-logs-*"
],
"auto_start": true,
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"format": "strict_date_optional_time"
}
}
},
"settings": {
"max_page_search_size": 5000
}
}

View file

@ -1,16 +0,0 @@
{
"modules": ["network_metrics"],
"indices": ["auditbeat-*"],
"auto_start": true,
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"format": "strict_date_optional_time"
}
}
},
"settings": {
"max_page_search_size": 5000
}
}

View file

@ -1,8 +0,0 @@
{
"modules": ["host_metrics"],
"indices": ["auditbeat-*"],
"auto_start": true,
"settings": {
"max_page_search_size": 5000
}
}

View file

@ -1,5 +0,0 @@
{
"modules": ["host_metrics"],
"indices": ["auditbeat-*"],
"prefix": ["default_"]
}

View file

@ -1,24 +0,0 @@
{
"modules": ["network_metrics", "network_entities"],
"indices": [
"auditbeat-*",
"endgame-*",
"filebeat-*",
"logs-*",
"packetbeat-*",
"winlogbeat-*",
"-*elastic-cloud-logs-*"
],
"auto_start": true,
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"format": "strict_date_optional_time"
}
}
},
"settings": {
"max_page_search_size": 5000
}
}

View file

@ -1,4 +0,0 @@
{
"modules": ["host_metrics", "host_entities"],
"indices": ["auditbeat-*"]
}

View file

@ -1,24 +0,0 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
# Uses a default if no argument is specified
FILE=${1:-./post_examples/one_module_auditbeat.json}
# Example: ./post_transforms.sh ./post_examples/one_module_auditbeat.json
# Example: ./post_transforms.sh ./post_examples/one_module_namespace_auditbeat.json
curl -s -k \
-H 'Content-Type: application/json' \
-H 'kbn-xsrf: 123' \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X POST ${KIBANA_URL}${SPACE_URL}/api/metrics_entities/transforms \
-d @${FILE} \
| jq .

View file

@ -1,13 +0,0 @@
#!/bin/sh
#
# 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.
#
set -e
./check_env_variables.sh
# TODO Make this work

View file

@ -1,38 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import { ModuleNames, installableMappings, installableTransforms } from '../modules';
import type { Logger } from '../../../../../src/core/server';
import { uninstallMappings } from './uninstall_mappings';
import { uninstallTransforms } from './uninstall_transforms';
interface DeleteTransformsOptions {
esClient: ElasticsearchClient;
logger: Logger;
modules: ModuleNames[];
prefix: string;
suffix: string;
}
export const deleteTransforms = async ({
esClient,
logger,
modules,
prefix,
suffix,
}: DeleteTransformsOptions): Promise<void> => {
for (const moduleName of modules) {
const mappings = installableMappings[moduleName];
const transforms = installableTransforms[moduleName];
await uninstallTransforms({ esClient, logger, prefix, suffix, transforms });
await uninstallMappings({ esClient, logger, mappings, prefix, suffix });
}
};

View file

@ -1,24 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import type { Logger } from '../../../../../src/core/server';
interface GetTransformsOptions {
esClient: ElasticsearchClient;
logger: Logger;
}
// TODO: Type the Promise<unknown> to a stronger type
export const getTransforms = async ({ esClient }: GetTransformsOptions): Promise<unknown> => {
const body = await esClient.transform.getTransform({
size: 1000,
transform_id: '*',
});
return body;
};

View file

@ -1,15 +0,0 @@
/*
* 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 * from './delete_transforms';
export * from './get_transforms';
export * from './install_mappings';
export * from './install_transforms';
export * from './metrics_entities_client';
export * from './post_transforms';
export * from './uninstall_mappings';
export * from './uninstall_transforms';

View file

@ -1,82 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import { Mappings } from '../modules/types';
import type { Logger } from '../../../../../src/core/server';
import {
computeMappingId,
getIndexExists,
logMappingDebug,
logMappingError,
logMappingInfo,
} from './utils';
interface CreateMappingOptions {
esClient: ElasticsearchClient;
mappings: Mappings[];
prefix: string;
suffix: string;
logger: Logger;
kibanaVersion: string;
}
export const installMappings = async ({
esClient,
kibanaVersion,
mappings,
prefix,
suffix,
logger,
}: CreateMappingOptions): Promise<void> => {
for (const mapping of mappings) {
const { index } = mapping.mappings._meta;
const mappingId = computeMappingId({ id: index, prefix, suffix });
const exists = await getIndexExists(esClient, mappingId);
const computedBody = {
...mapping,
...{
mappings: {
...mapping.mappings,
_meta: {
...mapping.mappings._meta,
...{
created_by: 'metrics_entities',
index: mappingId,
version: kibanaVersion,
},
},
},
},
};
if (!exists) {
try {
logMappingInfo({ id: mappingId, logger, message: 'does not exist, creating the mapping' });
await esClient.indices.create({
body: computedBody,
index: mappingId,
});
} catch (error) {
logMappingError({
error,
id: mappingId,
logger,
message: 'cannot install mapping',
postBody: computedBody,
});
}
} else {
logMappingDebug({
id: mappingId,
logger,
message: 'mapping already exists. It will not be recreated',
});
}
}
};

View file

@ -1,122 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import { Transforms } from '../modules/types';
import type { Logger } from '../../../../../src/core/server';
import {
computeMappingId,
computeTransformId,
getTransformExists,
logTransformDebug,
logTransformError,
logTransformInfo,
} from './utils';
interface CreateTransformOptions {
esClient: ElasticsearchClient;
transforms: Transforms[];
autoStart: boolean;
indices: string[];
frequency: string;
logger: Logger;
query: object;
docsPerSecond: number | undefined;
maxPageSearchSize: number;
sync: {
time: {
delay: string;
field: string;
};
};
prefix: string;
suffix: string;
}
export const installTransforms = async ({
autoStart,
esClient,
frequency,
indices,
docsPerSecond,
logger,
maxPageSearchSize,
prefix,
suffix,
transforms,
query,
sync,
}: CreateTransformOptions): Promise<void> => {
for (const transform of transforms) {
const destIndex = transform?.dest?.index ?? transform.id;
const computedMappingIndex = computeMappingId({ id: destIndex, prefix, suffix });
const { id, ...transformNoId } = {
...transform,
...{ source: { ...transform.source, index: indices, query } },
...{ dest: { ...transform.dest, index: computedMappingIndex } },
...{
settings: {
...transform.settings,
docs_per_second: docsPerSecond,
max_page_search_size: maxPageSearchSize,
},
},
frequency,
sync,
};
const computedName = computeTransformId({ id, prefix, suffix });
const exists = await getTransformExists(esClient, computedName);
if (!exists) {
try {
logTransformInfo({
id: computedName,
logger,
message: 'does not exist, creating the transform',
});
await esClient.transform.putTransform({
body: transformNoId,
defer_validation: true,
transform_id: computedName,
});
if (autoStart) {
logTransformInfo({
id: computedName,
logger,
message: 'is being auto started',
});
await esClient.transform.startTransform({
transform_id: computedName,
});
} else {
logTransformInfo({
id: computedName,
logger,
message: 'is not being auto started',
});
}
} catch (error) {
logTransformError({
error,
id: computedName,
logger,
message: 'Could not create and/or start',
postBody: transformNoId,
});
}
} else {
logTransformDebug({
id: computedName,
logger,
message: 'already exists. It will not be recreated',
});
}
}
};

View file

@ -1,76 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import type { Logger } from '../../../../../src/core/server';
import { getTransforms } from './get_transforms';
import {
ConstructorOptions,
DeleteTransformsOptions,
PostTransformsOptions,
} from './metrics_entities_client_types';
import { postTransforms } from './post_transforms';
import { deleteTransforms } from './delete_transforms';
export class MetricsEntitiesClient {
private readonly esClient: ElasticsearchClient;
private readonly logger: Logger;
private readonly kibanaVersion: string;
constructor({ esClient, logger, kibanaVersion }: ConstructorOptions) {
this.esClient = esClient;
this.logger = logger;
this.kibanaVersion = kibanaVersion;
}
// TODO: Type the unknown to be stronger
public getTransforms = async (): Promise<unknown> => {
const { esClient, logger } = this;
return getTransforms({ esClient, logger });
};
public postTransforms = async ({
autoStart,
frequency,
docsPerSecond,
maxPageSearchSize,
modules,
indices,
prefix,
suffix,
query,
sync,
}: PostTransformsOptions): Promise<void> => {
const { esClient, logger, kibanaVersion } = this;
return postTransforms({
autoStart,
docsPerSecond,
esClient,
frequency,
indices,
kibanaVersion,
logger,
maxPageSearchSize,
modules,
prefix,
query,
suffix,
sync,
});
};
public deleteTransforms = async ({
modules,
prefix,
suffix,
}: DeleteTransformsOptions): Promise<void> => {
const { esClient, logger } = this;
return deleteTransforms({ esClient, logger, modules, prefix, suffix });
};
}

View file

@ -1,41 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import type { Logger } from '../../../../../src/core/server';
import { ModuleNames } from '../modules';
export interface ConstructorOptions {
esClient: ElasticsearchClient;
logger: Logger;
kibanaVersion: string;
}
export interface PostTransformsOptions {
modules: ModuleNames[];
autoStart: boolean;
frequency: string;
indices: string[];
docsPerSecond: number | undefined;
maxPageSearchSize: number;
prefix: string;
query: object;
suffix: string;
sync: {
time: {
delay: string;
field: string;
};
};
}
export interface DeleteTransformsOptions {
modules: ModuleNames[];
prefix: string;
suffix: string;
}

View file

@ -1,72 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import { ModuleNames, installableMappings, installableTransforms } from '../modules';
import type { Logger } from '../../../../../src/core/server';
import { installMappings } from './install_mappings';
import { installTransforms } from './install_transforms';
interface PostTransformsOptions {
logger: Logger;
esClient: ElasticsearchClient;
modules: ModuleNames[];
autoStart: boolean;
frequency: string;
indices: string[];
docsPerSecond: number | undefined;
kibanaVersion: string;
maxPageSearchSize: number;
query: object;
prefix: string;
suffix: string;
sync: {
time: {
delay: string;
field: string;
};
};
}
export const postTransforms = async ({
autoStart,
logger,
esClient,
frequency,
indices,
docsPerSecond,
kibanaVersion,
maxPageSearchSize,
modules,
prefix,
suffix,
query,
sync,
}: PostTransformsOptions): Promise<void> => {
for (const moduleName of modules) {
const mappings = installableMappings[moduleName];
const transforms = installableTransforms[moduleName];
await installMappings({ esClient, kibanaVersion, logger, mappings, prefix, suffix });
await installTransforms({
autoStart,
docsPerSecond,
esClient,
frequency,
indices,
logger,
maxPageSearchSize,
prefix,
query,
suffix,
sync,
transforms,
});
}
};

View file

@ -1,8 +0,0 @@
/*
* 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.
*/
// TODO: Write this

View file

@ -1,55 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import { Mappings } from '../modules/types';
import type { Logger } from '../../../../../src/core/server';
import { computeMappingId, logMappingInfo } from './utils';
import { logMappingError } from './utils/log_mapping_error';
interface UninstallMappingOptions {
esClient: ElasticsearchClient;
mappings: Mappings[];
prefix: string;
suffix: string;
logger: Logger;
}
export const uninstallMappings = async ({
esClient,
logger,
mappings,
prefix,
suffix,
}: UninstallMappingOptions): Promise<void> => {
const indices = mappings.map((mapping) => {
const { index } = mapping.mappings._meta;
return computeMappingId({ id: index, prefix, suffix });
});
logMappingInfo({
id: indices.join(),
logger,
message: 'deleting indices',
});
try {
await esClient.indices.delete({
allow_no_indices: true,
ignore_unavailable: true,
index: indices,
});
} catch (error) {
logMappingError({
error,
id: indices.join(),
logger,
message: 'could not delete index',
postBody: undefined,
});
}
};

View file

@ -1,93 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
import { asyncForEach } from '@kbn/std';
import { Transforms } from '../modules/types';
import type { Logger } from '../../../../../src/core/server';
import {
computeTransformId,
getTransformExists,
logTransformError,
logTransformInfo,
} from './utils';
interface UninstallTransformsOptions {
esClient: ElasticsearchClient;
transforms: Transforms[];
prefix: string;
suffix: string;
logger: Logger;
}
/**
* Uninstalls all the transforms underneath a given module
*/
export const uninstallTransforms = async ({
esClient,
logger,
prefix,
suffix,
transforms,
}: UninstallTransformsOptions): Promise<void> => {
await asyncForEach(transforms, async (transform) => {
const { id } = transform;
const computedId = computeTransformId({ id, prefix, suffix });
const exists = await getTransformExists(esClient, computedId);
if (exists) {
logTransformInfo({
id: computedId,
logger,
message: 'stopping transform',
});
try {
await esClient.transform.stopTransform({
allow_no_match: true,
force: true,
timeout: '5s',
transform_id: computedId,
wait_for_completion: true,
});
} catch (error) {
logTransformError({
error,
id: computedId,
logger,
message: 'Could not stop transform, still attempting to delete it',
postBody: undefined,
});
}
logTransformInfo({
id: computedId,
logger,
message: 'deleting transform',
});
try {
await esClient.transform.deleteTransform({
force: true,
transform_id: computedId,
});
} catch (error) {
logTransformError({
error,
id: computedId,
logger,
message: 'Could not create and/or start',
postBody: undefined,
});
}
} else {
logTransformInfo({
id: computedId,
logger,
message: 'transform does not exist to delete',
});
}
});
};

View file

@ -1,24 +0,0 @@
/*
* 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 { computeTransformId } from './compute_transform_id';
export const computeMappingId = ({
prefix,
id,
suffix,
}: {
prefix: string;
id: string;
suffix: string;
}): string => {
// TODO: This causes issues if above 65 character limit. We should limit the prefix
// and anything else on the incoming routes to avoid this causing an issue. We should still
// throw here in case I change the prefix or other names and cause issues.
const computedId = computeTransformId({ id, prefix, suffix });
return `.${computedId}`;
};

View file

@ -1,33 +0,0 @@
/*
* 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 { ELASTIC_NAME } from '../../../common/constants';
export const computeTransformId = ({
prefix,
id,
suffix,
}: {
prefix: string;
id: string;
suffix: string;
}): string => {
const prefixExists = prefix.trim() !== '';
const suffixExists = suffix.trim() !== '';
// TODO: Check for invalid characters on the main route for prefixExists and suffixExists and do an invalidation
// if either have invalid characters for a job name. Might want to add that same check within the API too at a top level?
if (prefixExists && suffixExists) {
return `${ELASTIC_NAME}_${prefix}_${id}_${suffix}`;
} else if (prefixExists) {
return `${ELASTIC_NAME}_${prefix}_${id}`;
} else if (suffixExists) {
return `${ELASTIC_NAME}_${id}_${suffix}`;
} else {
return `${ELASTIC_NAME}_${id}`;
}
};

View file

@ -1,38 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
/**
* Tried and true, copied forever again and again, the way we check if an index exists
* with the least amount of privileges.
* @param esClient The client to check if the index already exists
* @param index The index to check for
* @returns true if it exists, otherwise false
*/
export const getIndexExists = async (
esClient: ElasticsearchClient,
index: string
): Promise<boolean> => {
try {
const response = await esClient.search({
allow_no_indices: true,
body: {
terminate_after: 1,
},
index,
size: 0,
});
return response._shards.total > 0;
} catch (err) {
if (err.body?.status === 404) {
return false;
} else {
throw err.body ? err.body : err;
}
}
};

View file

@ -1,10 +0,0 @@
/*
* 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.
*/
// TODO: Move indent to configuration part or flip to default false
export const getJSON = (body: unknown, indent: boolean = true): string =>
indent ? JSON.stringify(body, null, 2) : JSON.stringify(body);

View file

@ -1,27 +0,0 @@
/*
* 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 { ElasticsearchClient } from 'kibana/server';
export const getTransformExists = async (
esClient: ElasticsearchClient,
id: string
): Promise<boolean> => {
try {
const { count } = await esClient.transform.getTransform({
size: 1000,
transform_id: id,
});
return count > 0;
} catch (err) {
if (err.body?.status === 404) {
return false;
} else {
throw err.body ? err.body : err;
}
}
};

View file

@ -1,17 +0,0 @@
/*
* 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 * from './compute_mapping_index';
export * from './compute_transform_id';
export * from './get_index_exists';
export * from './get_transform_exists';
export * from './log_mapping_debug';
export * from './log_mapping_error';
export * from './log_mapping_info';
export * from './log_transform_debug';
export * from './log_transform_error';
export * from './log_transform_info';

View file

@ -1,20 +0,0 @@
/*
* 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 type { Logger } from '../../../../../../src/core/server';
export const logMappingDebug = ({
logger,
id,
message,
}: {
logger: Logger;
id: string;
message: string;
}): void => {
logger.debug(`mapping id: "${id}", ${message}`);
};

View file

@ -1,27 +0,0 @@
/*
* 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 type { Logger } from '../../../../../../src/core/server';
import { getJSON } from './get_json';
export const logMappingError = ({
logger,
id,
message,
error,
postBody,
}: {
logger: Logger;
id: string;
error: unknown;
message: string;
postBody: {} | undefined;
}): void => {
const postString = postBody != null ? `, post body: "${getJSON(postBody)}"` : '';
logger.error(`${message}, mapping id: "${id}"${postString}, error: ${getJSON(error)}`);
};

View file

@ -1,20 +0,0 @@
/*
* 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 type { Logger } from '../../../../../../src/core/server';
export const logMappingInfo = ({
logger,
id,
message,
}: {
logger: Logger;
id: string;
message: string;
}): void => {
logger.info(`mapping id: "${id}", ${message}`);
};

View file

@ -1,20 +0,0 @@
/*
* 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 type { Logger } from '../../../../../../src/core/server';
export const logTransformDebug = ({
logger,
id,
message,
}: {
logger: Logger;
id: string;
message: string;
}): void => {
logger.debug(`transform id: "${id}", ${message}`);
};

View file

@ -1,27 +0,0 @@
/*
* 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 type { Logger } from '../../../../../../src/core/server';
import { getJSON } from './get_json';
export const logTransformError = ({
id,
logger,
error,
postBody,
message,
}: {
logger: Logger;
id: string;
error: unknown;
message: string;
postBody: {} | undefined;
}): void => {
const postString = postBody != null ? `, post body: "${getJSON(postBody)}"` : '';
logger.error(`${message}, transform id: ${id}${postString}, response error: ${getJSON(error)}`);
};

View file

@ -1,20 +0,0 @@
/*
* 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 type { Logger } from '../../../../../../src/core/server';
export const logTransformInfo = ({
logger,
id,
message,
}: {
logger: Logger;
id: string;
message: string;
}): void => {
logger.info(`transform id: "${id}", ${message}`);
};

View file

@ -1,36 +0,0 @@
/*
* 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 { ElasticsearchClient, IContextProvider, RequestHandlerContext } from 'kibana/server';
import { MetricsEntitiesClient } from './services/metrics_entities_client';
export type GetMetricsEntitiesClientType = (esClient: ElasticsearchClient) => MetricsEntitiesClient;
export interface MetricsEntitiesPluginSetup {
getMetricsEntitiesClient: GetMetricsEntitiesClientType;
}
export type MetricsEntitiesPluginStart = void;
export type ContextProvider = IContextProvider<
MetricsEntitiesRequestHandlerContext,
'metricsEntities'
>;
export interface MetricsEntitiesApiRequestHandlerContext {
getMetricsEntitiesClient: () => MetricsEntitiesClient;
}
export interface MetricsEntitiesRequestHandlerContext extends RequestHandlerContext {
metricsEntities?: MetricsEntitiesApiRequestHandlerContext;
}
/**
* @internal
*/
export type ContextProviderReturn = Promise<MetricsEntitiesApiRequestHandlerContext>;

View file

@ -1,26 +0,0 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
// have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636
"server/**/*.json",
"../../../typings/**/*"
],
"references": [
{ "path": "../../../src/core/tsconfig.json" },
{ "path": "../spaces/tsconfig.json" },
{ "path": "../security/tsconfig.json" },
{ "path": "../licensing/tsconfig.json" },
{ "path": "../features/tsconfig.json" },
{ "path": "../../../src/plugins/usage_collection/tsconfig.json" },
{ "path": "../../../src/plugins/kibana_utils/tsconfig.json" }
]
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import type { TransformConfigSchema } from './transforms/types';
/**
* as const
*
@ -54,7 +52,6 @@ export const DEFAULT_INTERVAL_PAUSE = true as const;
export const DEFAULT_INTERVAL_TYPE = 'manual' as const;
export const DEFAULT_INTERVAL_VALUE = 300000 as const; // ms
export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges' as const;
export const DEFAULT_TRANSFORMS = 'securitySolution:transforms' as const;
export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled' as const;
export const GLOBAL_HEADER_HEIGHT = 96 as const; // px
export const FILTERS_GLOBAL_HEIGHT = 109 as const; // px
@ -203,38 +200,6 @@ export const IP_REPUTATION_LINKS_SETTING_DEFAULT = `[
{ "name": "talosIntelligence.com", "url_template": "https://talosintelligence.com/reputation_center/lookup?search={{ip}}" }
]`;
/** The default settings for the transforms */
export const defaultTransformsSetting: TransformConfigSchema = {
enabled: false,
auto_start: true,
auto_create: true,
query: {
range: {
'@timestamp': {
gte: 'now-1d/d',
format: 'strict_date_optional_time',
},
},
},
retention_policy: {
time: {
field: '@timestamp',
max_age: '1w',
},
},
max_page_search_size: 5000,
settings: [
{
prefix: 'all',
indices: ['auditbeat-*', 'endgame-*', 'filebeat-*', 'logs-*', 'packetbeat-*', 'winlogbeat-*'],
data_sources: [
['auditbeat-*', 'endgame-*', 'filebeat-*', 'logs-*', 'packetbeat-*', 'winlogbeat-*'],
],
},
],
};
export const DEFAULT_TRANSFORMS_SETTING = JSON.stringify(defaultTransformsSetting, null, 2);
/**
* Id for the notifications alerting type
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
@ -371,13 +336,6 @@ export const showAllOthersBucket: string[] = [
'user.name',
];
/**
* Used for transforms for metrics_entities. If the security_solutions pulls in
* the metrics_entities plugin, then it should pull this constant from there rather
* than use it from here.
*/
export const ELASTIC_NAME = 'estc' as const;
export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_' as const;
export const RISKY_USERS_INDEX_PREFIX = 'ml_user_risk_score_' as const;

View file

@ -12,7 +12,6 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues;
* This object is then used to validate and parse the value entered.
*/
export const allowedExperimentalValues = Object.freeze({
metricsEntitiesEnabled: false,
ruleRegistryEnabled: true,
tGridEnabled: true,
tGridEventRenderedViewEnabled: true,

View file

@ -17,7 +17,6 @@ export enum HostsQueries {
details = 'hostDetails',
firstOrLastSeen = 'firstOrLastSeen',
hosts = 'hosts',
hostsEntities = 'hostsEntities',
overview = 'overviewHost',
uncommonProcesses = 'uncommonProcesses',
}

View file

@ -12,8 +12,6 @@ import { HostsKpiHistogramData } from '../common';
export interface HostsKpiAuthenticationsHistogramCount {
doc_count: number;
// TODO: Should I keep this or split this interface into two for entities and non-entities?
value?: number;
}
export type HostsKpiAuthenticationsRequestOptions = RequestBasicOptions;

View file

@ -16,11 +16,8 @@ import { HostsKpiUniqueIpsStrategyResponse } from './unique_ips';
export enum HostsKpiQueries {
kpiAuthentications = 'hostsKpiAuthentications',
kpiAuthenticationsEntities = 'hostsKpiAuthenticationsEntities',
kpiHosts = 'hostsKpiHosts',
kpiHostsEntities = 'hostsKpiHostsEntities',
kpiUniqueIps = 'hostsKpiUniqueIps',
kpiUniqueIpsEntities = 'hostsKpiUniqueIpsEntities',
}
export type HostsKpiStrategyResponse =

View file

@ -59,7 +59,6 @@ import {
} from './network';
import {
MatrixHistogramQuery,
MatrixHistogramQueryEntities,
MatrixHistogramRequestOptions,
MatrixHistogramStrategyResponse,
} from './matrix_histogram';
@ -106,8 +105,7 @@ export type FactoryQueryTypes =
| NetworkKpiQueries
| RiskQueries
| CtiQueries
| typeof MatrixHistogramQuery
| typeof MatrixHistogramQueryEntities;
| typeof MatrixHistogramQuery;
export interface RequestBasicOptions extends IEsSearchRequest {
timerange: TimerangeInput;

View file

@ -25,11 +25,9 @@ export * from './events';
export * from './preview';
export const MatrixHistogramQuery = 'matrixHistogram';
export const MatrixHistogramQueryEntities = 'matrixHistogramEntities';
export enum MatrixHistogramType {
authentications = 'authentications',
authenticationsEntities = 'authenticationsEntities',
anomalies = 'anomalies',
events = 'events',
alerts = 'alerts',
@ -41,7 +39,6 @@ export const MatrixHistogramTypeToAggName = {
[MatrixHistogramType.alerts]: 'aggregations.alertsGroup.buckets',
[MatrixHistogramType.anomalies]: 'aggregations.anomalyActionGroup.buckets',
[MatrixHistogramType.authentications]: 'aggregations.eventActionGroup.buckets',
[MatrixHistogramType.authenticationsEntities]: 'aggregations.events.buckets',
[MatrixHistogramType.dns]: 'aggregations.dns_name_query_count.buckets',
[MatrixHistogramType.events]: 'aggregations.eventActionGroup.buckets',
[MatrixHistogramType.preview]: 'aggregations.preview.buckets',

View file

@ -23,8 +23,6 @@ export enum NetworkQueries {
overview = 'overviewNetwork',
tls = 'tls',
topCountries = 'topCountries',
topCountriesEntities = 'topCountriesEntities',
topNFlow = 'topNFlow',
topNFlowEntities = 'topNFlowEntities',
users = 'users',
}

View file

@ -19,14 +19,10 @@ import { NetworkKpiUniquePrivateIpsStrategyResponse } from './unique_private_ips
export enum NetworkKpiQueries {
dns = 'networkKpiDns',
dnsEntities = 'networkKpiDnsEntities',
networkEvents = 'networkKpiNetworkEvents',
networkEventsEntities = 'networkKpiNetworkEventsEntities',
tlsHandshakes = 'networkKpiTlsHandshakes',
tlsHandshakesEntities = 'networkKpiTlsHandshakesEntities',
uniqueFlows = 'networkKpiUniqueFlows',
uniquePrivateIps = 'networkKpiUniquePrivateIps',
uniquePrivateIpsEntities = 'networkKpiUniquePrivateIpsEntities',
}
export type NetworkKpiStrategyResponse =

Some files were not shown because too many files have changed in this diff Show more